Repository: nushell/engine-q
Pull request #366 (2021-11-24 18:27:04)

add random commands
This PR related to #242 

### TODO
- [x] random
- [x] bool
~~chars~~
~~decimal~~
~~dice~~
~~integer~~

jaeheonji: @arthur-targaryen 
https://github.com/nushell/engine-q/issues/242#issuecomment-978330232

Sorry to see your comment late! As you said, I open a PR for the progress so far.
The other commands will make PR individually as they progress!
Thanks :)

Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 208abba..ed8cfd3 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1434,6 +1434,7 @@ dependencies = [
  "chrono",
  "chrono-humanize",
  "chrono-tz",
+ "crossterm",
  "csv",
  "dialoguer",
  "eml-parser",
@@ -1441,10 +1442,10 @@ dependencies = [
  "ical",
  "indexmap",
  "itertools",
+ "lazy_static",
  "lscolors",
  "meval",
  "nu-ansi-term 0.39.0",
- "nu-dataframe",
  "nu-engine",
  "nu-json",
  "nu-parser",
@@ -1452,14 +1453,17 @@ dependencies = [
  "nu-protocol",
  "nu-table",
  "nu-term-grid",
+ "num",
  "polars",
  "rand",
  "rayon",
  "regex",
+ "roxmltree",
  "serde",
  "serde_ini",
  "serde_urlencoded",
  "serde_yaml",
+ "strip-ansi-escapes",
  "sysinfo",
  "terminal_size",
  "thiserror",
@@ -1469,19 +1473,6 @@ dependencies = [
  "unicode-segmentation",
 ]
 
-[[package]]
-name = "nu-dataframe"
-version = "0.1.0"
-dependencies = [
- "chrono",
- "indexmap",
- "nu-json",
- "nu-protocol",
- "num",
- "polars",
- "serde",
-]
-
 [[package]]
 name = "nu-engine"
 version = "0.1.0"
@@ -2182,6 +2173,15 @@ version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "194d8e591e405d1eecf28819740abed6d719d1a2db87fc0bcdedee9a26d55560"
 
+[[package]]
+name = "roxmltree"
+version = "0.14.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "921904a62e410e37e215c40381b7117f830d9d89ba60ab5236170541dd25646b"
+dependencies = [
+ "xmlparser",
+]
+
 [[package]]
 name = "rust-argon2"
 version = "0.8.3"
@@ -2771,6 +2771,12 @@ version = "0.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
 
+[[package]]
+name = "xmlparser"
+version = "0.13.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "114ba2b24d2167ef6d67d7d04c8cc86522b87f490025f39f0303b7db5bf5e3d8"
+
 [[package]]
 name = "yaml-rust"
 version = "0.4.5"
diff --git a/Cargo.toml b/Cargo.toml
index 12eb7e1..6861e2a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -13,7 +13,6 @@ members = [
 	"crates/nu-command",
 	"crates/nu-protocol",
 	"crates/nu-plugin",
-	"crates/nu-dataframe",
 	"crates/nu_plugin_inc",
 ]
 
@@ -38,9 +37,8 @@ ctrlc = "3.2.1"
 
 [features]
 plugin = ["nu-plugin", "nu-parser/plugin", "nu-command/plugin", "nu-protocol/plugin"]
-custom = ["nu-command/custom", "nu-protocol/custom"]
-dataframe = ["custom", "nu-command/dataframe"]
-default = ["plugin", "custom"]
+dataframe = ["nu-command/dataframe"]
+default = ["plugin"]
 
 [dev-dependencies]
 tempfile = "3.2.0"
diff --git a/crates/nu-command/Cargo.toml b/crates/nu-command/Cargo.toml
index e9924b3..ca96af7 100644
--- a/crates/nu-command/Cargo.toml
+++ b/crates/nu-command/Cargo.toml
@@ -13,10 +13,7 @@ nu-protocol = { path = "../nu-protocol" }
 nu-table = { path = "../nu-table" }
 nu-term-grid = { path = "../nu-term-grid" }
 nu-parser = { path = "../nu-parser" }
-nu-dataframe = { path = "../nu-dataframe", optional = true }
 nu-ansi-term = { path = "../nu-ansi-term" }
-trash = { version = "1.3.0", optional = true }
-unicode-segmentation = "1.8.0"
 
 # Potential dependencies for extras
 csv = "1.1.3"
@@ -45,15 +42,22 @@ toml = "0.5.8"
 itertools = "0.10.0"
 ical = "0.7.0"
 calamine = "0.18.0"
+roxmltree = "0.14.0"
 rand = "0.8"
+trash = { version = "1.3.0", optional = true }
+unicode-segmentation = "1.8.0"
+lazy_static = "1.4.0"
+strip-ansi-escapes = "0.1.1"
+crossterm = "0.22.1"
+
+num = {version="0.4.0", optional=true}
 
 [dependencies.polars]
 version = "0.17.0"
 optional = true
-features = ["default", "parquet", "json"]
+features = ["default", "parquet", "json", "serde", "object", "checked_arithmetic", "strings"]
 
 [features]
 trash-support = ["trash"]
 plugin = ["nu-parser/plugin"]
-custom = ["nu-protocol/custom"]
-dataframe = ["custom", "nu-dataframe", "polars"]
+dataframe = ["polars", "num"]
diff --git a/crates/nu-command/src/core_commands/debug.rs b/crates/nu-command/src/core_commands/debug.rs
index 78a52e6..72023ea 100644
--- a/crates/nu-command/src/core_commands/debug.rs
+++ b/crates/nu-command/src/core_commands/debug.rs
@@ -15,7 +15,7 @@ impl Command for Debug {
     }
 
     fn signature(&self) -> Signature {
-        Signature::build("describe").category(Category::Core)
+        Signature::build("debug").category(Category::Core)
     }
 
     fn run(
diff --git a/crates/nu-command/src/core_commands/help.rs b/crates/nu-command/src/core_commands/help.rs
index 3f86eb8..c03dfa2 100644
--- a/crates/nu-command/src/core_commands/help.rs
+++ b/crates/nu-command/src/core_commands/help.rs
@@ -187,7 +187,6 @@ fn help(
                 .into_pipeline_data(engine_state.ctrlc.clone()))
         } else {
             let mut name = String::new();
-            let mut output = String::new();
 
             for r in &rest {
                 if !name.is_empty() {
@@ -196,16 +195,15 @@ fn help(
                 name.push_str(&r.item);
             }
 
-            for cmd in full_commands {
-                if cmd.0.name == name {
-                    let help = get_full_help(&cmd.0, &cmd.1, engine_state);
-                    output.push_str(&help);
-                }
-            }
+            let output = full_commands
+                .iter()
+                .filter(|(signature, _, _)| signature.name == name)
+                .map(|(signature, examples, _)| get_full_help(signature, examples, engine_state))
+                .collect::<Vec<String>>();
 
             if !output.is_empty() {
                 Ok(Value::String {
-                    val: output,
+                    val: output.join("======================\n\n"),
                     span: call.head,
                 }
                 .into_pipeline_data())
diff --git a/crates/nu-dataframe/README.md b/crates/nu-command/src/dataframe/README.md
similarity index 100%
rename from crates/nu-dataframe/README.md
rename to crates/nu-command/src/dataframe/README.md
diff --git a/crates/nu-command/src/dataframe/mod.rs b/crates/nu-command/src/dataframe/mod.rs
index b9cd1bb..f77f2eb 100644
--- a/crates/nu-command/src/dataframe/mod.rs
+++ b/crates/nu-command/src/dataframe/mod.rs
@@ -1,5 +1,3 @@
-mod open;
-mod to_df;
+mod nu_dataframe;
 
-pub use open::OpenDataFrame;
-pub use to_df::ToDataFrame;
+pub use nu_dataframe::commands::{DataTypes, DescribeDF, OpenDataFrame, ToDataFrame};
diff --git a/crates/nu-dataframe/src/between_values.rs b/crates/nu-command/src/dataframe/nu_dataframe/between_values.rs
similarity index 94%
rename from crates/nu-dataframe/src/between_values.rs
rename to crates/nu-command/src/dataframe/nu_dataframe/between_values.rs
index 25e7135..528777b 100644
--- a/crates/nu-dataframe/src/between_values.rs
+++ b/crates/nu-command/src/dataframe/nu_dataframe/between_values.rs
@@ -1,6 +1,6 @@
 use super::{operations::Axis, NuDataFrame};
 
-use nu_protocol::{ast::Operator, ShellError, Span, Spanned, Value};
+use nu_protocol::{ast::Operator, span, ShellError, Span, Spanned, Value};
 use num::Zero;
 use polars::prelude::{
     BooleanType, ChunkCompare, ChunkedArray, DataType, Float64Type, Int64Type, IntoSeries,
@@ -10,15 +10,15 @@ use std::ops::{Add, BitAnd, BitOr, Div, Mul, Sub};
 
 pub fn between_dataframes(
     operator: Spanned<Operator>,
-    left: Value,
+    left: &Value,
     lhs: &NuDataFrame,
     right: &Value,
     rhs: &NuDataFrame,
-    operation_span: Span,
 ) -> Result<Value, ShellError> {
+    let operation_span = span(&[left.span()?, right.span()?]);
     match operator.item {
         Operator::Plus => match lhs.append_df(rhs, Axis::Row, operation_span) {
-            Ok(df) => Ok(df.to_value(operation_span)),
+            Ok(df) => Ok(df.into_value(operation_span)),
             Err(e) => Err(e),
         },
         _ => Err(ShellError::OperatorMismatch {
@@ -33,12 +33,12 @@ pub fn between_dataframes(
 
 pub fn compute_between_series(
     operator: Spanned<Operator>,
-    left: Value,
+    left: &Value,
     lhs: &Series,
     right: &Value,
     rhs: &Series,
-    operation_span: Span,
 ) -> Result<Value, ShellError> {
+    let operation_span = span(&[left.span()?, right.span()?]);
     match operator.item {
         Operator::Plus => {
             let mut res = lhs + rhs;
@@ -167,9 +167,8 @@ pub fn compute_between_series(
 
 pub fn compute_series_single_value(
     operator: Spanned<Operator>,
+    left: &Value,
     lhs: &NuDataFrame,
-    lhs_span: &Span,
-    left: Value,
     right: &Value,
 ) -> Result<Value, ShellError> {
     if !lhs.is_series() {
@@ -182,15 +181,16 @@ pub fn compute_series_single_value(
         });
     }
 
-    let lhs = lhs.as_series(*lhs_span)?;
+    let lhs_span = left.span()?;
+    let lhs = lhs.as_series(lhs_span)?;
 
     match operator.item {
         Operator::Plus => match &right {
             Value::Int { val, .. } => {
-                compute_series_i64(&lhs, *val, <ChunkedArray<Int64Type>>::add, *lhs_span)
+                compute_series_i64(&lhs, *val, <ChunkedArray<Int64Type>>::add, lhs_span)
             }
             Value::Float { val, .. } => {
-                compute_series_decimal(&lhs, *val, <ChunkedArray<Float64Type>>::add, *lhs_span)
+                compute_series_decimal(&lhs, *val, <ChunkedArray<Float64Type>>::add, lhs_span)
             }
             _ => Err(ShellError::OperatorMismatch {
                 op_span: operator.span,
@@ -202,10 +202,10 @@ pub fn compute_series_single_value(
         },
         Operator::Minus => match &right {
             Value::Int { val, .. } => {
-                compute_series_i64(&lhs, *val, <ChunkedArray<Int64Type>>::sub, *lhs_span)
+                compute_series_i64(&lhs, *val, <ChunkedArray<Int64Type>>::sub, lhs_span)
             }
             Value::Float { val, .. } => {
-                compute_series_decimal(&lhs, *val, <ChunkedArray<Float64Type>>::sub, *lhs_span)
+                compute_series_decimal(&lhs, *val, <ChunkedArray<Float64Type>>::sub, lhs_span)
             }
             _ => Err(ShellError::OperatorMismatch {
                 op_span: operator.span,
@@ -217,10 +217,10 @@ pub fn compute_series_single_value(
         },
         Operator::Multiply => match &right {
             Value::Int { val, .. } => {
-                compute_series_i64(&lhs, *val, <ChunkedArray<Int64Type>>::mul, *lhs_span)
+                compute_series_i64(&lhs, *val, <ChunkedArray<Int64Type>>::mul, lhs_span)
             }
             Value::Float { val, .. } => {
-                compute_series_decimal(&lhs, *val, <ChunkedArray<Float64Type>>::mul, *lhs_span)
+                compute_series_decimal(&lhs, *val, <ChunkedArray<Float64Type>>::mul, lhs_span)
             }
             _ => Err(ShellError::OperatorMismatch {
                 op_span: operator.span,
@@ -235,14 +235,14 @@ pub fn compute_series_single_value(
                 if *val == 0 {
                     Err(ShellError::DivisionByZero(*span))
                 } else {
-                    compute_series_i64(&lhs, *val, <ChunkedArray<Int64Type>>::div, *lhs_span)
+                    compute_series_i64(&lhs, *val, <ChunkedArray<Int64Type>>::div, lhs_span)
                 }
             }
             Value::Float { val, span } => {
                 if val.is_zero() {
                     Err(ShellError::DivisionByZero(*span))
                 } else {
-                    compute_series_decimal(&lhs, *val, <ChunkedArray<Float64Type>>::div, *lhs_span)
+                    compute_series_decimal(&lhs, *val, <ChunkedArray<Float64Type>>::div, lhs_span)
                 }
             }
             _ => Err(ShellError::OperatorMismatch {
@@ -254,9 +254,9 @@ pub fn compute_series_single_value(
             }),
         },
         Operator::Equal => match &right {
-            Value::Int { val, .. } => compare_series_i64(&lhs, *val, ChunkedArray::eq, *lhs_span),
+            Value::Int { val, .. } => compare_series_i64(&lhs, *val, ChunkedArray::eq, lhs_span),
             Value::Float { val, .. } => {
-                compare_series_decimal(&lhs, *val, ChunkedArray::eq, *lhs_span)
+                compare_series_decimal(&lhs, *val, ChunkedArray::eq, lhs_span)
             }
             _ => Err(ShellError::OperatorMismatch {
                 op_span: operator.span,
@@ -267,9 +267,9 @@ pub fn compute_series_single_value(
             }),
         },
         Operator::NotEqual => match &right {
-            Value::Int { val, .. } => compare_series_i64(&lhs, *val, ChunkedArray::neq, *lhs_span),
+            Value::Int { val, .. } => compare_series_i64(&lhs, *val, ChunkedArray::neq, lhs_span),
             Value::Float { val, .. } => {
-                compare_series_decimal(&lhs, *val, ChunkedArray::neq, *lhs_span)
+                compare_series_decimal(&lhs, *val, ChunkedArray::neq, lhs_span)
             }
             _ => Err(ShellError::OperatorMismatch {
                 op_span: operator.span,
@@ -280,9 +280,9 @@ pub fn compute_series_single_value(
             }),
         },
         Operator::LessThan => match &right {
-            Value::Int { val, .. } => compare_series_i64(&lhs, *val, ChunkedArray::lt, *lhs_span),
+            Value::Int { val, .. } => compare_series_i64(&lhs, *val, ChunkedArray::lt, lhs_span),
             Value::Float { val, .. } => {
-                compare_series_decimal(&lhs, *val, ChunkedArray::lt, *lhs_span)
+                compare_series_decimal(&lhs, *val, ChunkedArray::lt, lhs_span)
             }
             _ => Err(ShellError::OperatorMismatch {
                 op_span: operator.span,
@@ -293,11 +293,9 @@ pub fn compute_series_single_value(
             }),
         },
         Operator::LessThanOrEqual => match &right {
-            Value::Int { val, .. } => {
-                compare_series_i64(&lhs, *val, ChunkedArray::lt_eq, *lhs_span)
-            }
+            Value::Int { val, .. } => compare_series_i64(&lhs, *val, ChunkedArray::lt_eq, lhs_span),
             Value::Float { val, .. } => {
-                compare_series_decimal(&lhs, *val, ChunkedArray::lt_eq, *lhs_span)
+                compare_series_decimal(&lhs, *val, ChunkedArray::lt_eq, lhs_span)
             }
             _ => Err(ShellError::OperatorMismatch {
                 op_span: operator.span,
@@ -308,9 +306,9 @@ pub fn compute_series_single_value(
             }),
         },
         Operator::GreaterThan => match &right {
-            Value::Int { val, .. } => compare_series_i64(&lhs, *val, ChunkedArray::gt, *lhs_span),
+            Value::Int { val, .. } => compare_series_i64(&lhs, *val, ChunkedArray::gt, lhs_span),
             Value::Float { val, .. } => {
-                compare_series_decimal(&lhs, *val, ChunkedArray::gt, *lhs_span)
+                compare_series_decimal(&lhs, *val, ChunkedArray::gt, lhs_span)
             }
             _ => Err(ShellError::OperatorMismatch {
                 op_span: operator.span,
@@ -321,11 +319,9 @@ pub fn compute_series_single_value(
             }),
         },
         Operator::GreaterThanOrEqual => match &right {
-            Value::Int { val, .. } => {
-                compare_series_i64(&lhs, *val, ChunkedArray::gt_eq, *lhs_span)
-            }
+            Value::Int { val, .. } => compare_series_i64(&lhs, *val, ChunkedArray::gt_eq, lhs_span),
             Value::Float { val, .. } => {
-                compare_series_decimal(&lhs, *val, ChunkedArray::gt_eq, *lhs_span)
+                compare_series_decimal(&lhs, *val, ChunkedArray::gt_eq, lhs_span)
             }
             _ => Err(ShellError::OperatorMismatch {
                 op_span: operator.span,
@@ -336,7 +332,7 @@ pub fn compute_series_single_value(
             }),
         },
         Operator::Contains => match &right {
-            Value::String { val, .. } => contains_series_pat(&lhs, val, *lhs_span),
+            Value::String { val, .. } => contains_series_pat(&lhs, val, lhs_span),
             _ => Err(ShellError::OperatorMismatch {
                 op_span: operator.span,
                 lhs_ty: left.get_type(),
diff --git a/crates/nu-command/src/dataframe/nu_dataframe/commands/describe.rs b/crates/nu-command/src/dataframe/nu_dataframe/commands/describe.rs
new file mode 100644
index 0000000..1a0a06a
--- /dev/null
+++ b/crates/nu-command/src/dataframe/nu_dataframe/commands/describe.rs
@@ -0,0 +1,246 @@
+use crate::dataframe::nu_dataframe::Column;
+
+use super::super::NuDataFrame;
+
+use nu_protocol::{
+    ast::Call,
+    engine::{Command, EngineState, Stack},
+    Category, Example, PipelineData, ShellError, Signature, Span,
+};
+use polars::{
+    chunked_array::ChunkedArray,
+    prelude::{
+        AnyValue, DataFrame, DataType, Float64Type, IntoSeries, NewChunkedArray, Series, Utf8Type,
+    },
+};
+
+#[derive(Clone)]
+pub struct DescribeDF;
+
+impl Command for DescribeDF {
+    fn name(&self) -> &str {
+        "describe"
+    }
+
+    fn usage(&self) -> &str {
+        "Describes dataframes numeric columns"
+    }
+
+    fn signature(&self) -> Signature {
+        Signature::build(self.name().to_string()).category(Category::Custom("dataframe".into()))
+    }
+
+    fn examples(&self) -> Vec<Example> {
+        vec![Example {
+            description: "dataframe description",
+            example: "[[a b]; [1 1] [1 1]] | to df | describe",
+            result: Some(
+                NuDataFrame::try_from_columns(vec![
+                    Column::new(
+                        "descriptor".to_string(),
+                        vec![
+                            "count".to_string().into(),
+                            "sum".to_string().into(),
+                            "mean".to_string().into(),
+                            "median".to_string().into(),
+                            "std".to_string().into(),
+                            "min".to_string().into(),
+                            "25%".to_string().into(),
+                            "50%".to_string().into(),
+                            "75%".to_string().into(),
+                            "max".to_string().into(),
+                        ],
+                    ),
+                    Column::new(
+                        "a (i64)".to_string(),
+                        vec![
+                            2.0.into(),
+                            2.0.into(),
+                            1.0.into(),
+                            1.0.into(),
+                            0.0.into(),
+                            1.0.into(),
+                            1.0.into(),
+                            1.0.into(),
+                            1.0.into(),
+                            1.0.into(),
+                        ],
+                    ),
+                    Column::new(
+                        "b (i64)".to_string(),
+                        vec![
+                            2.0.into(),
+                            2.0.into(),
+                            1.0.into(),
+                            1.0.into(),
+                            0.0.into(),
+                            1.0.into(),
+                            1.0.into(),
+                            1.0.into(),
+                            1.0.into(),
+                            1.0.into(),
+                        ],
+                    ),
+                ])
+                .expect("simple df for test should not fail")
+                .into_value(Span::unknown()),
+            ),
+        }]
+    }
+
+    fn run(
+        &self,
+        engine_state: &EngineState,
+        stack: &mut Stack,
+        call: &Call,
+        input: PipelineData,
+    ) -> Result<PipelineData, ShellError> {
+        command(engine_state, stack, call, input)
+    }
+}
+
+fn command(
+    _engine_state: &EngineState,
+    _stack: &mut Stack,
+    call: &Call,
+    input: PipelineData,
+) -> Result<PipelineData, ShellError> {
+    let df = NuDataFrame::try_from_pipeline(input, call.head.clone())?;
+
+    let names = ChunkedArray::<Utf8Type>::new_from_opt_slice(
+        "descriptor",
+        &[
+            Some("count"),
+            Some("sum"),
+            Some("mean"),
+            Some("median"),
+            Some("std"),
+            Some("min"),
+            Some("25%"),
+            Some("50%"),
+            Some("75%"),
+            Some("max"),
+        ],
+    )
+    .into_series();
+
+    let head = std::iter::once(names);
+
+    let tail = df
+        .as_ref()
+        .get_columns()
+        .iter()
+        .filter(|col| col.dtype() != &DataType::Object("object"))
+        .map(|col| {
+            let count = col.len() as f64;
+
+            let sum = match col.sum_as_series().cast(&DataType::Float64) {
+                Ok(ca) => match ca.get(0) {
+                    AnyValue::Float64(v) => Some(v),
+                    _ => None,
+                },
+                Err(_) => None,
+            };
+
+            let mean = match col.mean_as_series().get(0) {
+                AnyValue::Float64(v) => Some(v),
+                _ => None,
+            };
+
+            let median = match col.median_as_series().get(0) {
+                AnyValue::Float64(v) => Some(v),
+                _ => None,
+            };
+
+            let std = match col.std_as_series().get(0) {
+                AnyValue::Float64(v) => Some(v),
+                _ => None,
+            };
+
+            let min = match col.min_as_series().cast(&DataType::Float64) {
+                Ok(ca) => match ca.get(0) {
+                    AnyValue::Float64(v) => Some(v),
+                    _ => None,
+                },
+                Err(_) => None,
+            };
+
+            let q_25 = match col.quantile_as_series(0.25) {
+                Ok(ca) => match ca.cast(&DataType::Float64) {
+                    Ok(ca) => match ca.get(0) {
+                        AnyValue::Float64(v) => Some(v),
+                        _ => None,
+                    },
+                    Err(_) => None,
+                },
+                Err(_) => None,
+            };
+
+            let q_50 = match col.quantile_as_series(0.50) {
+                Ok(ca) => match ca.cast(&DataType::Float64) {
+                    Ok(ca) => match ca.get(0) {
+                        AnyValue::Float64(v) => Some(v),
+                        _ => None,
+                    },
+                    Err(_) => None,
+                },
+                Err(_) => None,
+            };
+
+            let q_75 = match col.quantile_as_series(0.75) {
+                Ok(ca) => match ca.cast(&DataType::Float64) {
+                    Ok(ca) => match ca.get(0) {
+                        AnyValue::Float64(v) => Some(v),
+                        _ => None,
+                    },
+                    Err(_) => None,
+                },
+                Err(_) => None,
+            };
+
+            let max = match col.max_as_series().cast(&DataType::Float64) {
+                Ok(ca) => match ca.get(0) {
+                    AnyValue::Float64(v) => Some(v),
+                    _ => None,
+                },
+                Err(_) => None,
+            };
+
+            let name = format!("{} ({})", col.name(), col.dtype());
+            ChunkedArray::<Float64Type>::new_from_opt_slice(
+                &name,
+                &[
+                    Some(count),
+                    sum,
+                    mean,
+                    median,
+                    std,
+                    min,
+                    q_25,
+                    q_50,
+                    q_75,
+                    max,
+                ],
+            )
+            .into_series()
+        });
+
+    let res = head.chain(tail).collect::<Vec<Series>>();
+    let df = DataFrame::new(res).map_err(|e| {
+        ShellError::LabeledError("Dataframe Error".into(), e.to_string(), call.head)
+    })?;
+    Ok(PipelineData::Value(NuDataFrame::dataframe_into_value(
+        df, call.head,
+    )))
+}
+
+#[cfg(test)]
+mod test {
+    use super::super::test_dataframe::test_dataframe;
+    use super::*;
+
+    #[test]
+    fn test_examples() {
+        test_dataframe(DescribeDF {})
+    }
+}
diff --git a/crates/nu-command/src/dataframe/nu_dataframe/commands/dtypes.rs b/crates/nu-command/src/dataframe/nu_dataframe/commands/dtypes.rs
new file mode 100644
index 0000000..db6e7b5
--- /dev/null
+++ b/crates/nu-command/src/dataframe/nu_dataframe/commands/dtypes.rs
@@ -0,0 +1,106 @@
+use super::super::{Column, NuDataFrame};
+use nu_protocol::{
+    ast::Call,
+    engine::{Command, EngineState, Stack},
+    Category, Example, PipelineData, ShellError, Signature, Span, Value,
+};
+
+#[derive(Clone)]
+pub struct DataTypes;
+
+impl Command for DataTypes {
+    fn name(&self) -> &str {
+        "dtypes"
+    }
+
+    fn usage(&self) -> &str {
+        "Show dataframe data types"
+    }
+
+    fn signature(&self) -> Signature {
+        Signature::build(self.name().to_string()).category(Category::Custom("dataframe".into()))
+    }
+
+    fn examples(&self) -> Vec<Example> {
+        vec![Example {
+            description: "drop column a",
+            example: "[[a b]; [1 2] [3 4]] | to df | dtypes",
+            result: Some(
+                NuDataFrame::try_from_columns(vec![
+                    Column::new(
+                        "column".to_string(),
+                        vec!["a".to_string().into(), "b".to_string().into()],
+                    ),
+                    Column::new(
+                        "dtype".to_string(),
+                        vec!["i64".to_string().into(), "i64".to_string().into()],
+                    ),
+                ])
+                .expect("simple df for test should not fail")
+                .into_value(Span::unknown()),
+            ),
+        }]
+    }
+
+    fn run(
+        &self,
+        engine_state: &EngineState,
+        stack: &mut Stack,
+        call: &Call,
+        input: PipelineData,
+    ) -> Result<PipelineData, ShellError> {
+        command(engine_state, stack, call, input)
+    }
+}
+
+#[allow(clippy::needless_collect)]
+fn command(
+    _engine_state: &EngineState,
+    _stack: &mut Stack,
+    call: &Call,
+    input: PipelineData,
+) -> Result<PipelineData, ShellError> {
+    let df = NuDataFrame::try_from_pipeline(input, call.head.clone())?;
+
+    let mut dtypes: Vec<Value> = Vec::new();
+    let names: Vec<Value> = df
+        .as_ref()
+        .get_column_names()
+        .iter()
+        .map(|v| {
+            let dtype = df
+                .as_ref()
+                .column(v)
+                .expect("using name from list of names from dataframe")
+                .dtype();
+
+            let dtype_str = dtype.to_string();
+            dtypes.push(Value::String {
+                val: dtype_str.into(),
+                span: call.head,
+            });
+
+            Value::String {
+                val: v.to_string().into(),
+                span: call.head,
+            }
+        })
+        .collect();
+
+    let names_col = Column::new("column".to_string(), names);
+    let dtypes_col = Column::new("dtype".to_string(), dtypes);
+
+    let df = NuDataFrame::try_from_columns(vec![names_col, dtypes_col])?;
+    Ok(PipelineData::Value(df.into_value(call.head)))
+}
+
+#[cfg(test)]
+mod test {
+    use super::super::test_dataframe::test_dataframe;
+    use super::*;
+
+    #[test]
+    fn test_examples() {
+        test_dataframe(DataTypes {})
+    }
+}
diff --git a/crates/nu-command/src/dataframe/nu_dataframe/commands/mod.rs b/crates/nu-command/src/dataframe/nu_dataframe/commands/mod.rs
new file mode 100644
index 0000000..c08d349
--- /dev/null
+++ b/crates/nu-command/src/dataframe/nu_dataframe/commands/mod.rs
@@ -0,0 +1,12 @@
+mod describe;
+mod dtypes;
+mod open;
+mod to_df;
+
+pub use describe::DescribeDF;
+pub use dtypes::DataTypes;
+pub use open::OpenDataFrame;
+pub use to_df::ToDataFrame;
+
+#[cfg(test)]
+mod test_dataframe;
diff --git a/crates/nu-command/src/dataframe/open.rs b/crates/nu-command/src/dataframe/nu_dataframe/commands/open.rs
similarity index 97%
rename from crates/nu-command/src/dataframe/open.rs
rename to crates/nu-command/src/dataframe/nu_dataframe/commands/open.rs
index 1c926f1..d309676 100644
--- a/crates/nu-command/src/dataframe/open.rs
+++ b/crates/nu-command/src/dataframe/nu_dataframe/commands/open.rs
@@ -1,12 +1,11 @@
-use std::{fs::File, path::PathBuf};
-
-use nu_dataframe::NuDataFrame;
+use super::super::NuDataFrame;
 use nu_engine::CallExt;
 use nu_protocol::{
     ast::Call,
     engine::{Command, EngineState, Stack},
     Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape,
 };
+use std::{fs::File, path::PathBuf};
 
 use polars::prelude::{CsvEncoding, CsvReader, JsonReader, ParquetReader, SerReader};
 
@@ -23,7 +22,7 @@ impl Command for OpenDataFrame {
     }
 
     fn signature(&self) -> Signature {
-        Signature::build("open-df")
+        Signature::build(self.name().to_string())
             .required(
                 "file",
                 SyntaxShape::Filepath,
@@ -64,7 +63,7 @@ impl Command for OpenDataFrame {
     fn examples(&self) -> Vec<Example> {
         vec![Example {
             description: "Takes a file name and creates a dataframe",
-            example: "dataframe open test.csv",
+            example: "open-df test.csv",
             result: None,
         }]
     }
diff --git a/crates/nu-command/src/dataframe/nu_dataframe/commands/to_df.rs b/crates/nu-command/src/dataframe/nu_dataframe/commands/to_df.rs
new file mode 100644
index 0000000..6ce8845
--- /dev/null
+++ b/crates/nu-command/src/dataframe/nu_dataframe/commands/to_df.rs
@@ -0,0 +1,111 @@
+use super::super::{Column, NuDataFrame};
+
+use nu_protocol::{
+    ast::Call,
+    engine::{Command, EngineState, Stack},
+    Category, Example, PipelineData, ShellError, Signature, Span,
+};
+
+#[derive(Clone)]
+pub struct ToDataFrame;
+
+impl Command for ToDataFrame {
+    fn name(&self) -> &str {
+        "to df"
+    }
+
+    fn usage(&self) -> &str {
+        "Converts a List, Table or Dictionary into a dataframe"
+    }
+
+    fn signature(&self) -> Signature {
+        Signature::build(self.name().to_string()).category(Category::Custom("dataframe".into()))
+    }
+
+    fn examples(&self) -> Vec<Example> {
+        vec![
+            Example {
+                description: "Takes a dictionary and creates a dataframe",
+                example: "[[a b];[1 2] [3 4]] | to df",
+                result: Some(
+                    NuDataFrame::try_from_columns(vec![
+                        Column::new("a".to_string(), vec![1.into(), 3.into()]),
+                        Column::new("b".to_string(), vec![2.into(), 4.into()]),
+                    ])
+                    .expect("simple df for test should not fail")
+                    .into_value(Span::unknown()),
+                ),
+            },
+            Example {
+                description: "Takes a list of tables and creates a dataframe",
+                example: "[[1 2 a] [3 4 b] [5 6 c]] | to df",
+                result: Some(
+                    NuDataFrame::try_from_columns(vec![
+                        Column::new("0".to_string(), vec![1.into(), 3.into(), 5.into()]),
+                        Column::new("1".to_string(), vec![2.into(), 4.into(), 6.into()]),
+                        Column::new(
+                            "2".to_string(),
+                            vec![
+                                "a".to_string().into(),
+                                "b".to_string().into(),
+                                "c".to_string().into(),
+                            ],
+                        ),
+                    ])
+                    .expect("simple df for test should not fail")
+                    .into_value(Span::unknown()),
+                ),
+            },
+            Example {
+                description: "Takes a list and creates a dataframe",
+                example: "[a b c] | to df",
+                result: Some(
+                    NuDataFrame::try_from_columns(vec![Column::new(
+                        "0".to_string(),
+                        vec![
+                            "a".to_string().into(),
+                            "b".to_string().into(),
+                            "c".to_string().into(),
+                        ],
+                    )])
+                    .expect("simple df for test should not fail")
+                    .into_value(Span::unknown()),
+                ),
+            },
+            Example {
+                description: "Takes a list of booleans and creates a dataframe",
+                example: "[$true $true $false] | to df",
+                result: Some(
+                    NuDataFrame::try_from_columns(vec![Column::new(
+                        "0".to_string(),
+                        vec![true.into(), true.into(), false.into()],
+                    )])
+                    .expect("simple df for test should not fail")
+                    .into_value(Span::unknown()),
+                ),
+            },
+        ]
+    }
+
+    fn run(
+        &self,
+        _engine_state: &EngineState,
+        _stack: &mut Stack,
+        call: &Call,
+        input: PipelineData,
+    ) -> Result<PipelineData, ShellError> {
+        let df = NuDataFrame::try_from_iter(input.into_iter())?;
+        Ok(PipelineData::Value(NuDataFrame::into_value(df, call.head)))
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::super::test_dataframe::test_dataframe;
+    use super::*;
+
+    #[test]
+    fn test_examples() {
+        test_dataframe(ToDataFrame {})
+    }
+}
diff --git a/crates/nu-dataframe/src/conversion.rs b/crates/nu-command/src/dataframe/nu_dataframe/conversion.rs
similarity index 99%
rename from crates/nu-dataframe/src/conversion.rs
rename to crates/nu-command/src/dataframe/nu_dataframe/conversion.rs
index fdd834f..daccded 100644
--- a/crates/nu-dataframe/src/conversion.rs
+++ b/crates/nu-command/src/dataframe/nu_dataframe/conversion.rs
@@ -1,5 +1,5 @@
-use super::NuDataFrame;
-use crate::DataFrameValue;
+use super::{DataFrameValue, NuDataFrame};
+
 use chrono::{DateTime, FixedOffset, NaiveDateTime};
 use indexmap::map::{Entry, IndexMap};
 use nu_protocol::{ShellError, Span, Value};
@@ -35,9 +35,9 @@ impl Column {
         self.name.as_str()
     }
 
-    pub fn iter(&self) -> impl Iterator<Item = &Value> {
-        self.values.iter()
-    }
+    //pub fn iter(&self) -> impl Iterator<Item = &Value> {
+    //    self.values.iter()
+    //}
 }
 
 impl IntoIterator for Column {
diff --git a/crates/nu-dataframe/src/custom_value.rs b/crates/nu-command/src/dataframe/nu_dataframe/custom_value.rs
similarity index 71%
rename from crates/nu-dataframe/src/custom_value.rs
rename to crates/nu-command/src/dataframe/nu_dataframe/custom_value.rs
index 6ffb067..842d0b1 100644
--- a/crates/nu-dataframe/src/custom_value.rs
+++ b/crates/nu-command/src/dataframe/nu_dataframe/custom_value.rs
@@ -1,5 +1,5 @@
-use crate::NuDataFrame;
-use nu_protocol::{ast::Operator, CustomValue, ShellError, Span, Value};
+use super::NuDataFrame;
+use nu_protocol::{ast::Operator, Category, CustomValue, ShellError, Span, Value};
 
 // CustomValue implementation for NuDataFrame
 impl CustomValue for NuDataFrame {
@@ -20,6 +20,10 @@ impl CustomValue for NuDataFrame {
         }
     }
 
+    fn category(&self) -> Category {
+        Category::Custom(self.typetag_name().into())
+    }
+
     fn value_string(&self) -> String {
         self.typetag_name().to_string()
     }
@@ -44,7 +48,17 @@ impl CustomValue for NuDataFrame {
 
     fn follow_path_string(&self, column_name: String, span: Span) -> Result<Value, ShellError> {
         let column = self.column(&column_name, span)?;
-        Ok(column.to_value(span))
+        Ok(column.into_value(span))
+    }
+
+    fn partial_cmp(&self, other: &Value) -> Option<std::cmp::Ordering> {
+        match other {
+            Value::CustomValue { val, .. } => val
+                .as_any()
+                .downcast_ref::<Self>()
+                .and_then(|other| self.is_equal(other)),
+            _ => None,
+        }
     }
 
     fn operation(
diff --git a/crates/nu-dataframe/src/lib.rs b/crates/nu-command/src/dataframe/nu_dataframe/mod.rs
similarity index 70%
rename from crates/nu-dataframe/src/lib.rs
rename to crates/nu-command/src/dataframe/nu_dataframe/mod.rs
index 70196d6..8517836 100644
--- a/crates/nu-dataframe/src/lib.rs
+++ b/crates/nu-command/src/dataframe/nu_dataframe/mod.rs
@@ -1,15 +1,17 @@
+pub mod commands;
+
 mod between_values;
 mod conversion;
 mod custom_value;
 mod operations;
 
-use std::{cmp::Ordering, fmt::Display, hash::Hasher};
-
 use conversion::{Column, ColumnMap};
+
 use indexmap::map::IndexMap;
-use nu_protocol::{did_you_mean, ShellError, Span, Value};
-use polars::prelude::{DataFrame, PolarsObject, Series};
+use nu_protocol::{did_you_mean, PipelineData, ShellError, Span, Value};
+use polars::prelude::{DataFrame, DataType, PolarsObject, Series};
 use serde::{Deserialize, Serialize};
+use std::{cmp::Ordering, fmt::Display, hash::Hasher};
 
 // DataFrameValue is an encapsulation of Nushell Value that can be used
 // to define the PolarsObject Trait. The polars object trait allows to
@@ -62,13 +64,25 @@ impl std::hash::Hash for DataFrameValue {
 
 impl PolarsObject for DataFrameValue {
     fn type_name() -> &'static str {
-        "value"
+        "object"
     }
 }
 
 #[derive(Debug, Serialize, Deserialize)]
 pub struct NuDataFrame(DataFrame);
 
+impl AsRef<DataFrame> for NuDataFrame {
+    fn as_ref(&self) -> &polars::prelude::DataFrame {
+        &self.0
+    }
+}
+
+impl AsMut<DataFrame> for NuDataFrame {
+    fn as_mut(&mut self) -> &mut polars::prelude::DataFrame {
+        &mut self.0
+    }
+}
+
 impl NuDataFrame {
     pub fn new(dataframe: DataFrame) -> Self {
         Self(dataframe)
@@ -86,7 +100,7 @@ impl NuDataFrame {
         }
     }
 
-    pub fn to_value(self, span: Span) -> Value {
+    pub fn into_value(self, span: Span) -> Value {
         Value::CustomValue {
             val: Box::new(self),
             span,
@@ -131,12 +145,12 @@ impl NuDataFrame {
         conversion::from_parsed_columns(column_values)
     }
 
-    pub fn try_from_series(columns: Vec<Series>) -> Result<Self, ShellError> {
-        let dataframe = DataFrame::new(columns)
-            .map_err(|e| ShellError::InternalError(format!("Unable to create DataFrame: {}", e)))?;
+    //pub fn try_from_series(columns: Vec<Series>) -> Result<Self, ShellError> {
+    //    let dataframe = DataFrame::new(columns)
+    //        .map_err(|e| ShellError::InternalError(format!("Unable to create DataFrame: {}", e)))?;
 
-        Ok(Self::new(dataframe))
-    }
+    //    Ok(Self::new(dataframe))
+    //}
 
     pub fn try_from_columns(columns: Vec<Column>) -> Result<Self, ShellError> {
         let mut column_values: ColumnMap = IndexMap::new();
@@ -151,6 +165,24 @@ impl NuDataFrame {
         conversion::from_parsed_columns(column_values)
     }
 
+    pub fn try_from_pipeline(input: PipelineData, span: Span) -> Result<Self, ShellError> {
+        match input.into_value(span) {
+            Value::CustomValue { val, span } => match val.as_any().downcast_ref::<NuDataFrame>() {
+                Some(df) => Ok(NuDataFrame(df.0.clone())),
+                None => Err(ShellError::CantConvert(
+                    "Dataframe not found".into(),
+                    "value is not a dataframe".into(),
+                    span,
+                )),
+            },
+            _ => Err(ShellError::CantConvert(
+                "Dataframe not found".into(),
+                "value is not a dataframe".into(),
+                span,
+            )),
+        }
+    }
+
     pub fn column(&self, column: &str, span: Span) -> Result<Self, ShellError> {
         let s = self.0.column(column).map_err(|_| {
             let possibilities = self
@@ -295,4 +327,64 @@ impl NuDataFrame {
 
         Ok(values)
     }
+
+    // Dataframes are considered equal if they have the same shape, column name and values
+    pub fn is_equal(&self, other: &Self) -> Option<Ordering> {
+        if self.as_ref().width() == 0 {
+            // checking for empty dataframe
+            return None;
+        }
+
+        if self.as_ref().get_column_names() != other.as_ref().get_column_names() {
+            // checking both dataframes share the same names
+            return None;
+        }
+
+        if self.as_ref().height() != other.as_ref().height() {
+            // checking both dataframes have the same row size
+            return None;
+        }
+
+        // sorting dataframe by the first column
+        let column_names = self.as_ref().get_column_names();
+        let first_col = column_names
+            .get(0)
+            .expect("already checked that dataframe is different than 0");
+
+        // if unable to sort, then unable to compare
+        let lhs = match self.as_ref().sort(*first_col, false) {
+            Ok(df) => df,
+            Err(_) => return None,
+        };
+
+        let rhs = match other.as_ref().sort(*first_col, false) {
+            Ok(df) => df,
+            Err(_) => return None,
+        };
+
+        for name in self.as_ref().get_column_names() {
+            let self_series = lhs.column(name).expect("name from dataframe names");
+
+            let other_series = rhs
+                .column(name)
+                .expect("already checked that name in other");
+
+            let self_series = match self_series.dtype() {
+                // Casting needed to compare other numeric types with nushell numeric type.
+                // In nushell we only have i64 integer numeric types and any array created
+                // with nushell untagged primitives will be of type i64
+                DataType::UInt32 => match self_series.cast(&DataType::Int64) {
+                    Ok(series) => series,
+                    Err(_) => return None,
+                },
+                _ => self_series.clone(),
+            };
+
+            if !self_series.series_equal(other_series) {
+                return None;
+            }
+        }
+
+        Some(Ordering::Equal)
+    }
 }
diff --git a/crates/nu-dataframe/src/operations.rs b/crates/nu-command/src/dataframe/nu_dataframe/operations.rs
similarity index 61%
rename from crates/nu-dataframe/src/operations.rs
rename to crates/nu-command/src/dataframe/nu_dataframe/operations.rs
index bcd57f3..c37db6c 100644
--- a/crates/nu-dataframe/src/operations.rs
+++ b/crates/nu-command/src/dataframe/nu_dataframe/operations.rs
@@ -1,7 +1,7 @@
-use nu_protocol::{ast::Operator, span, ShellError, Span, Spanned, Value};
+use nu_protocol::{ast::Operator, ShellError, Span, Spanned, Value};
 use polars::prelude::{DataFrame, Series};
 
-use crate::between_values::{
+use super::between_values::{
     between_dataframes, compute_between_series, compute_series_single_value,
 };
 
@@ -9,18 +9,18 @@ use super::NuDataFrame;
 
 pub enum Axis {
     Row,
-    Column,
+    //Column,
 }
 
-impl Axis {
-    pub fn try_from_str(axis: &str, span: Span) -> Result<Axis, ShellError> {
-        match axis {
-            "row" => Ok(Axis::Row),
-            "col" => Ok(Axis::Column),
-            _ => Err(ShellError::DidYouMean("'row' or 'col'".into(), span)),
-        }
-    }
-}
+//impl Axis {
+//    pub fn try_from_str(axis: &str, span: Span) -> Result<Axis, ShellError> {
+//        match axis {
+//            "row" => Ok(Axis::Row),
+//            "col" => Ok(Axis::Column),
+//            _ => Err(ShellError::DidYouMean("'row' or 'col'".into(), span)),
+//        }
+//    }
+//}
 
 impl NuDataFrame {
     pub fn compute_with_value(
@@ -42,7 +42,6 @@ impl NuDataFrame {
                     )
                 })?;
 
-                let operation_span = span(&[lhs_span, *rhs_span]);
                 match (self.is_series(), rhs.is_series()) {
                     (true, true) => {
                         let lhs = &self
@@ -77,11 +76,10 @@ impl NuDataFrame {
 
                         compute_between_series(
                             op,
-                            NuDataFrame::default_value(lhs_span),
+                            &NuDataFrame::default_value(lhs_span),
                             lhs,
                             right,
                             rhs,
-                            operation_span,
                         )
                     }
                     _ => {
@@ -101,11 +99,10 @@ impl NuDataFrame {
 
                         between_dataframes(
                             op,
-                            NuDataFrame::default_value(lhs_span),
+                            &NuDataFrame::default_value(lhs_span),
                             self,
                             right,
                             rhs,
-                            operation_span,
                         )
                     }
                 }
@@ -116,13 +113,7 @@ impl NuDataFrame {
                     span: op_span,
                 };
 
-                compute_series_single_value(
-                    op,
-                    self,
-                    &lhs_span,
-                    NuDataFrame::default_value(lhs_span),
-                    right,
-                )
+                compute_series_single_value(op, &NuDataFrame::default_value(lhs_span), self, right)
             }
         }
     }
@@ -131,7 +122,7 @@ impl NuDataFrame {
         &self,
         other: &NuDataFrame,
         axis: Axis,
-        span: Span,
+        _span: Span,
     ) -> Result<Self, ShellError> {
         match axis {
             Axis::Row => {
@@ -160,61 +151,60 @@ impl NuDataFrame {
                     .map_err(|e| ShellError::InternalError(e.to_string()))?;
 
                 Ok(NuDataFrame::new(df_new))
-            }
-            Axis::Column => {
-                if self.0.width() != other.0.width() {
-                    return Err(ShellError::IncompatibleParametersSingle(
-                        "Dataframes with different number of columns".into(),
-                        span,
-                    ));
-                }
-
-                if !self
-                    .0
-                    .get_column_names()
-                    .iter()
-                    .all(|col| other.0.get_column_names().contains(col))
-                {
-                    return Err(ShellError::IncompatibleParametersSingle(
-                        "Dataframes with different columns names".into(),
-                        span,
-                    ));
-                }
-
-                let new_cols = self
-                    .0
-                    .get_columns()
-                    .iter()
-                    .map(|s| {
-                        let other_col = other
-                            .0
-                            .column(s.name())
-                            .expect("Already checked that dataframes have same columns");
-
-                        let mut tmp = s.clone();
-                        let res = tmp.append(other_col);
-
-                        match res {
-                            Ok(s) => Ok(s.clone()),
-                            Err(e) => Err({
-                                ShellError::InternalError(format!(
-                                    "Unable to append dataframes: {}",
-                                    e
-                                ))
-                            }),
-                        }
-                    })
-                    .collect::<Result<Vec<Series>, ShellError>>()?;
-
-                let df_new = DataFrame::new(new_cols).map_err(|e| {
-                    ShellError::InternalError(format!(
-                        "Unable to append dataframes: {}",
-                        e.to_string()
-                    ))
-                })?;
-
-                Ok(NuDataFrame::new(df_new))
-            }
+            } //Axis::Column => {
+              //    if self.0.width() != other.0.width() {
+              //        return Err(ShellError::IncompatibleParametersSingle(
+              //            "Dataframes with different number of columns".into(),
+              //            span,
+              //        ));
+              //    }
+
+              //    if !self
+              //        .0
+              //        .get_column_names()
+              //        .iter()
+              //        .all(|col| other.0.get_column_names().contains(col))
+              //    {
+              //        return Err(ShellError::IncompatibleParametersSingle(
+              //            "Dataframes with different columns names".into(),
+              //            span,
+              //        ));
+              //    }
+
+              //    let new_cols = self
+              //        .0
+              //        .get_columns()
+              //        .iter()
+              //        .map(|s| {
+              //            let other_col = other
+              //                .0
+              //                .column(s.name())
+              //                .expect("Already checked that dataframes have same columns");
+
+              //            let mut tmp = s.clone();
+              //            let res = tmp.append(other_col);
+
+              //            match res {
+              //                Ok(s) => Ok(s.clone()),
+              //                Err(e) => Err({
+              //                    ShellError::InternalError(format!(
+              //                        "Unable to append dataframes: {}",
+              //                        e
+              //                    ))
+              //                }),
+              //            }
+              //        })
+              //        .collect::<Result<Vec<Series>, ShellError>>()?;
+
+              //    let df_new = DataFrame::new(new_cols).map_err(|e| {
+              //        ShellError::InternalError(format!(
+              //            "Unable to append dataframes: {}",
+              //            e.to_string()
+              //        ))
+              //    })?;
+
+              //    Ok(NuDataFrame::new(df_new))
+              //}
         }
     }
 }
diff --git a/crates/nu-command/src/dataframe/to_df.rs b/crates/nu-command/src/dataframe/to_df.rs
deleted file mode 100644
index a9cd503..0000000
--- a/crates/nu-command/src/dataframe/to_df.rs
+++ /dev/null
@@ -1,59 +0,0 @@
-use nu_dataframe::NuDataFrame;
-use nu_protocol::{
-    ast::Call,
-    engine::{Command, EngineState, Stack},
-    Category, Example, PipelineData, ShellError, Signature,
-};
-
-#[derive(Clone)]
-pub struct ToDataFrame;
-
-impl Command for ToDataFrame {
-    fn name(&self) -> &str {
-        "to-df"
-    }
-
-    fn usage(&self) -> &str {
-        "Converts a List, Table or Dictionary into a dataframe"
-    }
-
-    fn signature(&self) -> Signature {
-        Signature::build("to-df").category(Category::Custom("dataframe".into()))
-    }
-
-    fn examples(&self) -> Vec<Example> {
-        vec![
-            Example {
-                description: "Takes a dictionary and creates a dataframe",
-                example: "[[a b];[1 2] [3 4]] | to-df",
-                result: None,
-            },
-            Example {
-                description: "Takes a list of tables and creates a dataframe",
-                example: "[[1 2 a] [3 4 b] [5 6 c]] | to-df",
-                result: None,
-            },
-            Example {
-                description: "Takes a list and creates a dataframe",
-                example: "[a b c] | to-df",
-                result: None,
-            },
-            Example {
-                description: "Takes a list of booleans and creates a dataframe",
-                example: "[$true $true $false] | to-df",
-                result: None,
-            },
-        ]
-    }
-
-    fn run(
-        &self,
-        _engine_state: &EngineState,
-        _stack: &mut Stack,
-        call: &Call,
-        input: PipelineData,
-    ) -> Result<PipelineData, ShellError> {
-        let df = NuDataFrame::try_from_iter(input.into_iter())?;
-        Ok(PipelineData::Value(NuDataFrame::to_value(df, call.head)))
-    }
-}
diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs
index a61f47d..7b4e93e 100644
--- a/crates/nu-command/src/default_context.rs
+++ b/crates/nu-command/src/default_context.rs
@@ -17,6 +17,13 @@ pub fn create_default_context() -> EngineState {
             };
         }
 
+        // If there are commands that have the same name as default declarations,
+        // they have to be registered before the main declarations. This helps to make
+        // them only accessible if the correct input value category is used with the
+        // declaration
+        #[cfg(feature = "dataframe")]
+        bind_command!(DataTypes, DescribeDF, OpenDataFrame, ToDataFrame);
+
         // TODO: sort default context items categorically
         bind_command!(
             Alias,
@@ -64,6 +71,8 @@ pub fn create_default_context() -> EngineState {
             FromIcs,
             FromIni,
             FromVcf,
+            FromSsv,
+            FromXml,
             FromXlsx,
             Get,
             Griddle,
@@ -75,6 +84,7 @@ pub fn create_default_context() -> EngineState {
             IntoFilesize,
             IntoInt,
             IntoString,
+            Kill,
             Last,
             Length,
             Let,
@@ -104,11 +114,16 @@ pub fn create_default_context() -> EngineState {
             Parse,
             Ps,
             Range,
+            Random,
             Reverse,
             Rm,
             Select,
             Shuffle,
             Size,
+            Skip,
+            SkipUntil,
+            SkipWhile,
+            Sleep,
             Source,
             Split,
             SplitChars,
@@ -148,9 +163,6 @@ pub fn create_default_context() -> EngineState {
         #[cfg(feature = "plugin")]
         bind_command!(Register);
 
-        #[cfg(feature = "dataframe")]
-        bind_command!(OpenDataFrame, ToDataFrame);
-
         // This is a WIP proof of concept
         // bind_command!(ListGitBranches, Git, GitCheckout, Source);
 
diff --git a/crates/nu-command/src/filters/all.rs b/crates/nu-command/src/filters/all.rs
index e4ea5d5..8389789 100644
--- a/crates/nu-command/src/filters/all.rs
+++ b/crates/nu-command/src/filters/all.rs
@@ -1,6 +1,6 @@
 use nu_engine::eval_block;
 use nu_protocol::{
-    ast::{Call, Expr, Expression},
+    ast::Call,
     engine::{Command, EngineState, Stack},
     Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, SyntaxShape,
 };
@@ -52,13 +52,9 @@ impl Command for All {
         input: PipelineData,
     ) -> Result<PipelineData, ShellError> {
         let predicate = &call.positional[0];
-        let block_id = match predicate {
-            Expression {
-                expr: Expr::RowCondition(block_id),
-                ..
-            } => *block_id,
-            _ => return Err(ShellError::InternalError("Expected row condition".into())),
-        };
+        let block_id = predicate
+            .as_row_condition_block()
+            .ok_or_else(|| ShellError::InternalError("Expected row condition".to_owned()))?;
 
         let span = call.head;
 
diff --git a/crates/nu-command/src/filters/any.rs b/crates/nu-command/src/filters/any.rs
index 7cb4519..36bfe91 100644
--- a/crates/nu-command/src/filters/any.rs
+++ b/crates/nu-command/src/filters/any.rs
@@ -1,6 +1,6 @@
 use nu_engine::eval_block;
 use nu_protocol::{
-    ast::{Call, Expr, Expression},
+    ast::Call,
     engine::{Command, EngineState, Stack},
     Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, SyntaxShape,
 };
@@ -52,13 +52,9 @@ impl Command for Any {
         input: PipelineData,
     ) -> Result<PipelineData, ShellError> {
         let predicate = &call.positional[0];
-        let block_id = match predicate {
-            Expression {
-                expr: Expr::RowCondition(block_id),
-                ..
-            } => *block_id,
-            _ => return Err(ShellError::InternalError("Expected row condition".into())),
-        };
+        let block_id = predicate
+            .as_row_condition_block()
+            .ok_or_else(|| ShellError::InternalError("Expected row condition".to_owned()))?;
 
         let span = call.head;
 
diff --git a/crates/nu-command/src/filters/mod.rs b/crates/nu-command/src/filters/mod.rs
index 98e1f8c..61f4f15 100644
--- a/crates/nu-command/src/filters/mod.rs
+++ b/crates/nu-command/src/filters/mod.rs
@@ -14,6 +14,7 @@ mod range;
 mod reverse;
 mod select;
 mod shuffle;
+mod skip;
 mod update;
 mod where_;
 mod wrap;
@@ -35,6 +36,7 @@ pub use range::Range;
 pub use reverse::Reverse;
 pub use select::Select;
 pub use shuffle::Shuffle;
+pub use skip::*;
 pub use update::Update;
 pub use where_::Where;
 pub use wrap::Wrap;
diff --git a/crates/nu-command/src/filters/skip/command.rs b/crates/nu-command/src/filters/skip/command.rs
new file mode 100644
index 0000000..9a416af
--- /dev/null
+++ b/crates/nu-command/src/filters/skip/command.rs
@@ -0,0 +1,90 @@
+use std::convert::TryInto;
+
+use nu_engine::CallExt;
+use nu_protocol::{
+    ast::Call,
+    engine::{Command, EngineState, Stack},
+    Category, Example, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Span,
+    SyntaxShape, Value,
+};
+
+#[derive(Clone)]
+pub struct Skip;
+
+impl Command for Skip {
+    fn name(&self) -> &str {
+        "skip"
+    }
+
+    fn signature(&self) -> Signature {
+        Signature::build(self.name())
+            .optional("n", SyntaxShape::Int, "the number of elements to skip")
+            .category(Category::Filters)
+    }
+
+    fn usage(&self) -> &str {
+        "Skip the first n elements of the input."
+    }
+
+    fn examples(&self) -> Vec<Example> {
+        vec![
+            Example {
+                description: "Skip two elements",
+                example: "echo [[editions]; [2015] [2018] [2021]] | skip 2",
+                result: Some(Value::List {
+                    vals: vec![Value::Record {
+                        cols: vec!["editions".to_owned()],
+                        vals: vec![Value::from(2021)],
+                        span: Span::unknown(),
+                    }],
+                    span: Span::unknown(),
+                }),
+            },
+            Example {
+                description: "Skip the first value",
+                example: "echo [2 4 6 8] | skip",
+                result: Some(Value::List {
+                    vals: vec![Value::from(4), Value::from(6), Value::from(8)],
+                    span: Span::unknown(),
+                }),
+            },
+        ]
+    }
+
+    fn run(
+        &self,
+        engine_state: &EngineState,
+        stack: &mut Stack,
+        call: &Call,
+        input: PipelineData,
+    ) -> Result<PipelineData, ShellError> {
+        let n: Option<Value> = call.opt(engine_state, stack, 0)?;
+
+        let n: usize = match n {
+            Some(Value::Int { val, span }) => val.try_into().map_err(|err| {
+                ShellError::UnsupportedInput(
+                    format!("Could not convert {} to unsigned integer: {}", val, err),
+                    span,
+                )
+            })?,
+            Some(_) => return Err(ShellError::InternalError("Expected integer".into())),
+            None => 1,
+        };
+
+        let ctrlc = engine_state.ctrlc.clone();
+
+        Ok(input.into_iter().skip(n).into_pipeline_data(ctrlc))
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_examples() {
+        use crate::test_examples;
+
+        test_examples(Skip {})
+    }
+}
diff --git a/crates/nu-command/src/filters/skip/mod.rs b/crates/nu-command/src/filters/skip/mod.rs
new file mode 100644
index 0000000..164cf50
--- /dev/null
+++ b/crates/nu-command/src/filters/skip/mod.rs
@@ -0,0 +1,7 @@
+mod command;
+mod until;
+mod while_;
+
+pub use command::Skip;
+pub use until::SkipUntil;
+pub use while_::SkipWhile;
diff --git a/crates/nu-command/src/filters/skip/until.rs b/crates/nu-command/src/filters/skip/until.rs
new file mode 100644
index 0000000..5c09adc
--- /dev/null
+++ b/crates/nu-command/src/filters/skip/until.rs
@@ -0,0 +1,89 @@
+use nu_engine::eval_block;
+use nu_protocol::{
+    ast::Call,
+    engine::{Command, EngineState, Stack},
+    Category, Example, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Span,
+    SyntaxShape, Value,
+};
+
+#[derive(Clone)]
+pub struct SkipUntil;
+
+impl Command for SkipUntil {
+    fn name(&self) -> &str {
+        "skip until"
+    }
+
+    fn signature(&self) -> Signature {
+        Signature::build(self.name())
+            .required(
+                "predicate",
+                SyntaxShape::RowCondition,
+                "the predicate that skipped element must not match",
+            )
+            .category(Category::Filters)
+    }
+
+    fn usage(&self) -> &str {
+        "Skip elements of the input until a predicate is true."
+    }
+
+    fn examples(&self) -> Vec<Example> {
+        vec![Example {
+            description: "Skip until the element is positive",
+            example: "echo [-2 0 2 -1] | skip until $it > 0",
+            result: Some(Value::List {
+                vals: vec![Value::from(2), Value::from(-1)],
+                span: Span::unknown(),
+            }),
+        }]
+    }
+
+    fn run(
+        &self,
+        engine_state: &EngineState,
+        stack: &mut Stack,
+        call: &Call,
+        input: PipelineData,
+    ) -> Result<PipelineData, ShellError> {
+        let predicate = &call.positional[0];
+        let block_id = predicate
+            .as_row_condition_block()
+            .ok_or_else(|| ShellError::InternalError("Expected row condition".to_owned()))?;
+
+        let span = call.head;
+
+        let block = engine_state.get_block(block_id).clone();
+        let var_id = block.signature.get_positional(0).and_then(|arg| arg.var_id);
+        let mut stack = stack.collect_captures(&block.captures);
+
+        let ctrlc = engine_state.ctrlc.clone();
+        let engine_state = engine_state.clone();
+
+        Ok(input
+            .into_iter()
+            .skip_while(move |value| {
+                if let Some(var_id) = var_id {
+                    stack.add_var(var_id, value.clone());
+                }
+
+                !eval_block(&engine_state, &mut stack, &block, PipelineData::new(span))
+                    .map_or(false, |pipeline_data| {
+                        pipeline_data.into_value(span).is_true()
+                    })
+            })
+            .into_pipeline_data(ctrlc))
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_examples() {
+        use crate::test_examples;
+
+        test_examples(SkipUntil)
+    }
+}
diff --git a/crates/nu-command/src/filters/skip/while_.rs b/crates/nu-command/src/filters/skip/while_.rs
new file mode 100644
index 0000000..cc125d4
--- /dev/null
+++ b/crates/nu-command/src/filters/skip/while_.rs
@@ -0,0 +1,89 @@
+use nu_engine::eval_block;
+use nu_protocol::{
+    ast::Call,
+    engine::{Command, EngineState, Stack},
+    Category, Example, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Span,
+    SyntaxShape, Value,
+};
+
+#[derive(Clone)]
+pub struct SkipWhile;
+
+impl Command for SkipWhile {
+    fn name(&self) -> &str {
+        "skip while"
+    }
+
+    fn signature(&self) -> Signature {
+        Signature::build(self.name())
+            .required(
+                "predicate",
+                SyntaxShape::RowCondition,
+                "the predicate that skipped element must match",
+            )
+            .category(Category::Filters)
+    }
+
+    fn usage(&self) -> &str {
+        "Skip elements of the input while a predicate is true."
+    }
+
+    fn examples(&self) -> Vec<Example> {
+        vec![Example {
+            description: "Skip while the element is negative",
+            example: "echo [-2 0 2 -1] | skip while $it < 0",
+            result: Some(Value::List {
+                vals: vec![Value::from(0), Value::from(2), Value::from(-1)],
+                span: Span::unknown(),
+            }),
+        }]
+    }
+
+    fn run(
+        &self,
+        engine_state: &EngineState,
+        stack: &mut Stack,
+        call: &Call,
+        input: PipelineData,
+    ) -> Result<PipelineData, ShellError> {
+        let predicate = &call.positional[0];
+        let block_id = predicate
+            .as_row_condition_block()
+            .ok_or_else(|| ShellError::InternalError("Expected row condition".to_owned()))?;
+
+        let span = call.head;
+
+        let block = engine_state.get_block(block_id).clone();
+        let var_id = block.signature.get_positional(0).and_then(|arg| arg.var_id);
+        let mut stack = stack.collect_captures(&block.captures);
+
+        let ctrlc = engine_state.ctrlc.clone();
+        let engine_state = engine_state.clone();
+
+        Ok(input
+            .into_iter()
+            .skip_while(move |value| {
+                if let Some(var_id) = var_id {
+                    stack.add_var(var_id, value.clone());
+                }
+
+                eval_block(&engine_state, &mut stack, &block, PipelineData::new(span))
+                    .map_or(false, |pipeline_data| {
+                        pipeline_data.into_value(span).is_true()
+                    })
+            })
+            .into_pipeline_data(ctrlc))
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_examples() {
+        use crate::test_examples;
+
+        test_examples(SkipWhile)
+    }
+}
diff --git a/crates/nu-command/src/filters/where_.rs b/crates/nu-command/src/filters/where_.rs
index c411d2f..7adc547 100644
--- a/crates/nu-command/src/filters/where_.rs
+++ b/crates/nu-command/src/filters/where_.rs
@@ -1,5 +1,5 @@
 use nu_engine::eval_block;
-use nu_protocol::ast::{Call, Expr, Expression};
+use nu_protocol::ast::Call;
 use nu_protocol::engine::{Command, EngineState, Stack};
 use nu_protocol::{Category, PipelineData, ShellError, Signature, SyntaxShape};
 
@@ -29,19 +29,14 @@ impl Command for Where {
         input: PipelineData,
     ) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
         let span = call.head;
-        let cond = call.positional[0].clone();
+        let cond = &call.positional[0];
+        let block_id = cond
+            .as_row_condition_block()
+            .ok_or_else(|| ShellError::InternalError("Expected row condition".to_owned()))?;
 
         let ctrlc = engine_state.ctrlc.clone();
         let engine_state = engine_state.clone();
 
-        let block_id = match cond {
-            Expression {
-                expr: Expr::RowCondition(block_id),
-                ..
-            } => block_id,
-            _ => return Err(ShellError::InternalError("Expected row condition".into())),
-        };
-
         let block = engine_state.get_block(block_id).clone();
         let mut stack = stack.collect_captures(&block.captures);
 
diff --git a/crates/nu-command/src/formats/from/mod.rs b/crates/nu-command/src/formats/from/mod.rs
index 4698e75..54f007d 100644
--- a/crates/nu-command/src/formats/from/mod.rs
+++ b/crates/nu-command/src/formats/from/mod.rs
@@ -6,11 +6,13 @@ mod ics;
 mod ini;
 mod json;
 mod ods;
+mod ssv;
 mod toml;
 mod tsv;
 mod url;
 mod vcf;
 mod xlsx;
+mod xml;
 mod yaml;
 
 pub use self::csv::FromCsv;
@@ -21,9 +23,11 @@ pub use ics::FromIcs;
 pub use ini::FromIni;
 pub use json::FromJson;
 pub use ods::FromOds;
+pub use ssv::FromSsv;
 pub use tsv::FromTsv;
 pub use url::FromUrl;
 pub use vcf::FromVcf;
 pub use xlsx::FromXlsx;
+pub use xml::FromXml;
 pub use yaml::FromYaml;
 pub use yaml::FromYml;
diff --git a/crates/nu-command/src/formats/from/ssv.rs b/crates/nu-command/src/formats/from/ssv.rs
new file mode 100644
index 0000000..f213fdb
--- /dev/null
+++ b/crates/nu-command/src/formats/from/ssv.rs
@@ -0,0 +1,500 @@
+use indexmap::map::IndexMap;
+use nu_engine::CallExt;
+use nu_protocol::ast::Call;
+use nu_protocol::engine::{Command, EngineState, Stack};
+use nu_protocol::{
+    Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Spanned,
+    SyntaxShape, Value,
+};
+
+#[derive(Clone)]
+pub struct FromSsv;
+
+const DEFAULT_MINIMUM_SPACES: usize = 2;
+
+impl Command for FromSsv {
+    fn name(&self) -> &str {
+        "from ssv"
+    }
+
+    fn signature(&self) -> Signature {
+        Signature::build("from ssv")
+            .switch(
+                "noheaders",
+                "don't treat the first row as column names",
+                Some('n'),
+            )
+            .switch("aligned-columns", "assume columns are aligned", Some('a'))
+            .named(
+                "minimum-spaces",
+                SyntaxShape::Int,
+                "the minimum spaces to separate columns",
+                Some('m'),
+            )
+            .category(Category::Formats)
+    }
+
+    fn usage(&self) -> &str {
+        "Parse text as space-separated values and create a table. The default minimum number of spaces counted as a separator is 2."
+    }
+
+    fn examples(&self) -> Vec<Example> {
+        vec![Example {
+            example: r#"'FOO   BAR
+1   2' | from ssv"#,
+            description: "Converts ssv formatted string to table",
+            result: Some(Value::List { vals: vec![Value::Record { cols: vec!["FOO".to_string(), "BAR".to_string()], vals: vec![Value::String { val: "1".to_string(), span: Span::unknown() }, Value::String { val: "2".to_string(), span: Span::unknown() }], span: Span::unknown() }], span: Span::unknown() }),
+        }, Example {
+            example: r#"'FOO   BAR
+1   2' | from ssv -n"#,
+            description: "Converts ssv formatted string to table but not treating the first row as column names",
+            result: Some(
+                Value::List { vals: vec![Value::Record { cols: vec!["Column1".to_string(), "Column2".to_string()], vals: vec![Value::String { val: "FOO".to_string(), span: Span::unknown() }, Value::String { val: "BAR".to_string(), span: Span::unknown() }], span: Span::unknown() }, Value::Record { cols: vec!["Column1".to_string(), "Column2".to_string()], vals: vec![Value::String { val: "1".to_string(), span: Span::unknown() }, Value::String { val: "2".to_string(), span: Span::unknown() }], span: Span::unknown() }], span: Span::unknown() }),
+        }]
+    }
+
+    fn run(
+        &self,
+        engine_state: &EngineState,
+        stack: &mut Stack,
+        call: &Call,
+        input: PipelineData,
+    ) -> Result<nu_protocol::PipelineData, ShellError> {
+        from_ssv(engine_state, stack, call, input)
+    }
+}
+
+enum HeaderOptions<'a> {
+    WithHeaders(&'a str),
+    WithoutHeaders,
+}
+
+fn parse_aligned_columns<'a>(
+    lines: impl Iterator<Item = &'a str>,
+    headers: HeaderOptions,
+    separator: &str,
+) -> Vec<Vec<(String, String)>> {
+    fn construct<'a>(
+        lines: impl Iterator<Item = &'a str>,
+        headers: Vec<(String, usize)>,
+    ) -> Vec<Vec<(String, String)>> {
+        lines
+            .map(|l| {
+                headers
+                    .iter()
+                    .enumerate()
+                    .map(|(i, (header_name, start_position))| {
+                        let val = match headers.get(i + 1) {
+                            Some((_, end)) => {
+                                if *end < l.len() {
+                                    l.get(*start_position..*end)
+                                } else {
+                                    l.get(*start_position..)
+                                }
+                            }
+                            None => l.get(*start_position..),
+                        }
+                        .unwrap_or("")
+                        .trim()
+                        .into();
+                        (header_name.clone(), val)
+                    })
+                    .collect()
+            })
+            .collect()
+    }
+
+    let find_indices = |line: &str| {
+        let values = line
+            .split(&separator)
+            .map(str::trim)
+            .filter(|s| !s.is_empty());
+        values
+            .fold(
+                (0, vec![]),
+                |(current_pos, mut indices), value| match line[current_pos..].find(value) {
+                    None => (current_pos, indices),
+                    Some(index) => {
+                        let absolute_index = current_pos + index;
+                        indices.push(absolute_index);
+                        (absolute_index + value.len(), indices)
+                    }
+                },
+            )
+            .1
+    };
+
+    let parse_with_headers = |lines, headers_raw: &str| {
+        let indices = find_indices(headers_raw);
+        let headers = headers_raw
+            .split(&separator)
+            .map(str::trim)
+            .filter(|s| !s.is_empty())
+            .map(String::from)
+            .zip(indices);
+
+        let columns = headers.collect::<Vec<(String, usize)>>();
+
+        construct(lines, columns)
+    };
+
+    let parse_without_headers = |ls: Vec<&str>| {
+        let mut indices = ls
+            .iter()
+            .flat_map(|s| find_indices(*s))
+            .collect::<Vec<usize>>();
+
+        indices.sort_unstable();
+        indices.dedup();
+
+        let headers: Vec<(String, usize)> = indices
+            .iter()
+            .enumerate()
+            .map(|(i, position)| (format!("Column{}", i + 1), *position))
+            .collect();
+
+        construct(ls.iter().map(|s| s.to_owned()), headers)
+    };
+
+    match headers {
+        HeaderOptions::WithHeaders(headers_raw) => parse_with_headers(lines, headers_raw),
+        HeaderOptions::WithoutHeaders => parse_without_headers(lines.collect()),
+    }
+}
+
+fn parse_separated_columns<'a>(
+    lines: impl Iterator<Item = &'a str>,
+    headers: HeaderOptions,
+    separator: &str,
+) -> Vec<Vec<(String, String)>> {
+    fn collect<'a>(
+        headers: Vec<String>,
+        rows: impl Iterator<Item = &'a str>,
+        separator: &str,
+    ) -> Vec<Vec<(String, String)>> {
+        rows.map(|r| {
+            headers
+                .iter()
+                .zip(r.split(separator).map(str::trim).filter(|s| !s.is_empty()))
+                .map(|(a, b)| (a.to_owned(), b.to_owned()))
+                .collect()
+        })
+        .collect()
+    }
+
+    let parse_with_headers = |lines, headers_raw: &str| {
+        let headers = headers_raw
+            .split(&separator)
+            .map(str::trim)
+            .map(str::to_owned)
+            .filter(|s| !s.is_empty())
+            .collect();
+        collect(headers, lines, separator)
+    };
+
+    let parse_without_headers = |ls: Vec<&str>| {
+        let num_columns = ls.iter().map(|r| r.len()).max().unwrap_or(0);
+
+        let headers = (1..=num_columns)
+            .map(|i| format!("Column{}", i))
+            .collect::<Vec<String>>();
+        collect(headers, ls.into_iter(), separator)
+    };
+
+    match headers {
+        HeaderOptions::WithHeaders(headers_raw) => parse_with_headers(lines, headers_raw),
+        HeaderOptions::WithoutHeaders => parse_without_headers(lines.collect()),
+    }
+}
+
+fn string_to_table(
+    s: &str,
+    noheaders: bool,
+    aligned_columns: bool,
+    split_at: usize,
+) -> Vec<Vec<(String, String)>> {
+    let mut lines = s.lines().filter(|l| !l.trim().is_empty());
+    let separator = " ".repeat(std::cmp::max(split_at, 1));
+
+    let (ls, header_options) = if noheaders {
+        (lines, HeaderOptions::WithoutHeaders)
+    } else {
+        match lines.next() {
+            Some(header) => (lines, HeaderOptions::WithHeaders(header)),
+            None => return vec![],
+        }
+    };
+
+    let f = if aligned_columns {
+        parse_aligned_columns
+    } else {
+        parse_separated_columns
+    };
+
+    f(ls, header_options, &separator)
+}
+
+fn from_ssv_string_to_value(
+    s: &str,
+    noheaders: bool,
+    aligned_columns: bool,
+    split_at: usize,
+    span: Span,
+) -> Value {
+    let rows = string_to_table(s, noheaders, aligned_columns, split_at)
+        .iter()
+        .map(|row| {
+            let mut dict = IndexMap::new();
+            for (col, entry) in row {
+                dict.insert(
+                    col.to_string(),
+                    Value::String {
+                        val: entry.to_string(),
+                        span,
+                    },
+                );
+            }
+            Value::from(Spanned { item: dict, span })
+        })
+        .collect();
+
+    Value::List { vals: rows, span }
+}
+
+fn from_ssv(
+    engine_state: &EngineState,
+    stack: &mut Stack,
+    call: &Call,
+    input: PipelineData,
+) -> Result<PipelineData, ShellError> {
+    let config = stack.get_config()?;
+    let name = call.head;
+
+    let noheaders = call.has_flag("noheaders");
+    let aligned_columns = call.has_flag("aligned-columns");
+    let minimum_spaces: Option<Spanned<usize>> =
+        call.get_flag(engine_state, stack, "minimum-spaces")?;
+
+    let concat_string = input.collect_string("", &config);
+    let split_at = match minimum_spaces {
+        Some(number) => number.item,
+        None => DEFAULT_MINIMUM_SPACES,
+    };
+
+    Ok(
+        from_ssv_string_to_value(&concat_string, noheaders, aligned_columns, split_at, name)
+            .into_pipeline_data(),
+    )
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    fn owned(x: &str, y: &str) -> (String, String) {
+        (String::from(x), String::from(y))
+    }
+
+    #[test]
+    fn it_trims_empty_and_whitespace_only_lines() {
+        let input = r#"
+
+            a       b
+
+            1       2
+
+            3       4
+        "#;
+        let result = string_to_table(input, false, true, 1);
+        assert_eq!(
+            result,
+            vec![
+                vec![owned("a", "1"), owned("b", "2")],
+                vec![owned("a", "3"), owned("b", "4")]
+            ]
+        );
+    }
+
+    #[test]
+    fn it_deals_with_single_column_input() {
+        let input = r#"
+            a
+            1
+            2
+        "#;
+        let result = string_to_table(input, false, true, 1);
+        assert_eq!(result, vec![vec![owned("a", "1")], vec![owned("a", "2")]]);
+    }
+
+    #[test]
+    fn it_uses_first_row_as_data_when_noheaders() {
+        let input = r#"
+            a b
+            1 2
+            3 4
+        "#;
+        let result = string_to_table(input, true, true, 1);
+        assert_eq!(
+            result,
+            vec![
+                vec![owned("Column1", "a"), owned("Column2", "b")],
+                vec![owned("Column1", "1"), owned("Column2", "2")],
+                vec![owned("Column1", "3"), owned("Column2", "4")]
+            ]
+        );
+    }
+
+    #[test]
+    fn it_allows_a_predefined_number_of_spaces() {
+        let input = r#"
+            column a   column b
+            entry 1    entry number  2
+            3          four
+        "#;
+
+        let result = string_to_table(input, false, true, 3);
+        assert_eq!(
+            result,
+            vec![
+                vec![
+                    owned("column a", "entry 1"),
+                    owned("column b", "entry number  2")
+                ],
+                vec![owned("column a", "3"), owned("column b", "four")]
+            ]
+        );
+    }
+
+    #[test]
+    fn it_trims_remaining_separator_space() {
+        let input = r#"
+            colA   colB     colC
+            val1   val2     val3
+        "#;
+
+        let trimmed = |s: &str| s.trim() == s;
+
+        let result = string_to_table(input, false, true, 2);
+        assert!(result
+            .iter()
+            .all(|row| row.iter().all(|(a, b)| trimmed(a) && trimmed(b))));
+    }
+
+    #[test]
+    fn it_keeps_empty_columns() {
+        let input = r#"
+            colA   col B     col C
+                   val2      val3
+            val4   val 5     val 6
+            val7             val8
+        "#;
+
+        let result = string_to_table(input, false, true, 2);
+        assert_eq!(
+            result,
+            vec![
+                vec![
+                    owned("colA", ""),
+                    owned("col B", "val2"),
+                    owned("col C", "val3")
+                ],
+                vec![
+                    owned("colA", "val4"),
+                    owned("col B", "val 5"),
+                    owned("col C", "val 6")
+                ],
+                vec![
+                    owned("colA", "val7"),
+                    owned("col B", ""),
+                    owned("col C", "val8")
+                ],
+            ]
+        );
+    }
+
+    #[test]
+    fn it_can_produce_an_empty_stream_for_header_only_input() {
+        let input = "colA   col B";
+
+        let result = string_to_table(input, false, true, 2);
+        let expected: Vec<Vec<(String, String)>> = vec![];
+        assert_eq!(expected, result);
+    }
+
+    #[test]
+    fn it_uses_the_full_final_column() {
+        let input = r#"
+            colA   col B
+            val1   val2   trailing value that should be included
+        "#;
+
+        let result = string_to_table(input, false, true, 2);
+        assert_eq!(
+            result,
+            vec![vec![
+                owned("colA", "val1"),
+                owned("col B", "val2   trailing value that should be included"),
+            ]]
+        );
+    }
+
+    #[test]
+    fn it_handles_empty_values_when_noheaders_and_aligned_columns() {
+        let input = r#"
+            a multi-word value  b           d
+            1                        3-3    4
+                                                       last
+        "#;
+
+        let result = string_to_table(input, true, true, 2);
+        assert_eq!(
+            result,
+            vec![
+                vec![
+                    owned("Column1", "a multi-word value"),
+                    owned("Column2", "b"),
+                    owned("Column3", ""),
+                    owned("Column4", "d"),
+                    owned("Column5", "")
+                ],
+                vec![
+                    owned("Column1", "1"),
+                    owned("Column2", ""),
+                    owned("Column3", "3-3"),
+                    owned("Column4", "4"),
+                    owned("Column5", "")
+                ],
+                vec![
+                    owned("Column1", ""),
+                    owned("Column2", ""),
+                    owned("Column3", ""),
+                    owned("Column4", ""),
+                    owned("Column5", "last")
+                ],
+            ]
+        );
+    }
+
+    #[test]
+    fn input_is_parsed_correctly_if_either_option_works() {
+        let input = r#"
+                docker-registry   docker-registry=default                   docker-registry=default   172.30.78.158   5000/TCP
+                kubernetes        component=apiserver,provider=kubernetes   <none>                    172.30.0.2      443/TCP
+                kubernetes-ro     component=apiserver,provider=kubernetes   <none>                    172.30.0.1      80/TCP
+            "#;
+
+        let aligned_columns_noheaders = string_to_table(input, true, true, 2);
+        let separator_noheaders = string_to_table(input, true, false, 2);
+        let aligned_columns_with_headers = string_to_table(input, false, true, 2);
+        let separator_with_headers = string_to_table(input, false, false, 2);
+        assert_eq!(aligned_columns_noheaders, separator_noheaders);
+        assert_eq!(aligned_columns_with_headers, separator_with_headers);
+    }
+
+    #[test]
+    fn test_examples() {
+        use crate::test_examples;
+
+        test_examples(FromSsv {})
+    }
+}
diff --git a/crates/nu-command/src/formats/from/xml.rs b/crates/nu-command/src/formats/from/xml.rs
new file mode 100644
index 0000000..8c579f6
--- /dev/null
+++ b/crates/nu-command/src/formats/from/xml.rs
@@ -0,0 +1,379 @@
+use indexmap::map::IndexMap;
+use nu_protocol::ast::Call;
+use nu_protocol::engine::{Command, EngineState, Stack};
+use nu_protocol::{
+    Category, Config, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span,
+    Spanned, Value,
+};
+
+#[derive(Clone)]
+pub struct FromXml;
+
+impl Command for FromXml {
+    fn name(&self) -> &str {
+        "from xml"
+    }
+
+    fn signature(&self) -> Signature {
+        Signature::build("from xml").category(Category::Formats)
+    }
+
+    fn usage(&self) -> &str {
+        "Parse text as .xml and create table."
+    }
+
+    fn run(
+        &self,
+        _engine_state: &EngineState,
+        stack: &mut Stack,
+        call: &Call,
+        input: PipelineData,
+    ) -> Result<nu_protocol::PipelineData, ShellError> {
+        let head = call.head;
+        let config = stack.get_config()?;
+        from_xml(input, head, &config)
+    }
+
+    fn examples(&self) -> Vec<Example> {
+        vec![Example {
+            example: r#"'<?xml version="1.0" encoding="UTF-8"?>
+<note>
+  <remember>Event</remember>
+</note>' | from xml"#,
+            description: "Converts xml formatted string to table",
+            result: Some(Value::Record {
+                cols: vec!["note".to_string()],
+                vals: vec![Value::Record {
+                    cols: vec!["children".to_string(), "attributes".to_string()],
+                    vals: vec![
+                        Value::List {
+                            vals: vec![Value::Record {
+                                cols: vec!["remember".to_string()],
+                                vals: vec![Value::Record {
+                                    cols: vec!["children".to_string(), "attributes".to_string()],
+                                    vals: vec![
+                                        Value::List {
+                                            vals: vec![Value::String {
+                                                val: "Event".to_string(),
+                                                span: Span::unknown(),
+                                            }],
+                                            span: Span::unknown(),
+                                        },
+                                        Value::Record {
+                                            cols: vec![],
+                                            vals: vec![],
+                                            span: Span::unknown(),
+                                        },
+                                    ],
+                                    span: Span::unknown(),
+                                }],
+                                span: Span::unknown(),
+                            }],
+                            span: Span::unknown(),
+                        },
+                        Value::Record {
+                            cols: vec![],
+                            vals: vec![],
+                            span: Span::unknown(),
+                        },
+                    ],
+                    span: Span::unknown(),
+                }],
+                span: Span::unknown(),
+            }),
+        }]
+    }
+}
+
+fn from_attributes_to_value(attributes: &[roxmltree::Attribute], span: Span) -> Value {
+    let mut collected = IndexMap::new();
+    for a in attributes {
+        collected.insert(String::from(a.name()), Value::string(a.value(), span));
+    }
+
+    let (cols, vals) = collected
+        .into_iter()
+        .fold((vec![], vec![]), |mut acc, (k, v)| {
+            acc.0.push(k);
+            acc.1.push(v);
+            acc
+        });
+
+    Value::Record { cols, vals, span }
+}
+
+fn from_node_to_value(n: &roxmltree::Node, span: Span) -> Value {
+    if n.is_element() {
+        let name = n.tag_name().name().trim().to_string();
+
+        let mut children_values = vec![];
+        for c in n.children() {
+            children_values.push(from_node_to_value(&c, span));
+        }
+
+        let children_values: Vec<Value> = children_values
+            .into_iter()
+            .filter(|x| match x {
+                Value::String { val: f, .. } => {
+                    !f.trim().is_empty() // non-whitespace characters?
+                }
+                _ => true,
+            })
+            .collect();
+
+        let mut collected = IndexMap::new();
+
+        let attribute_value: Value = from_attributes_to_value(n.attributes(), span);
+
+        let mut row = IndexMap::new();
+        row.insert(
+            String::from("children"),
+            Value::List {
+                vals: children_values,
+                span,
+            },
+        );
+        row.insert(String::from("attributes"), attribute_value);
+        collected.insert(name, Value::from(Spanned { item: row, span }));
+
+        Value::from(Spanned {
+            item: collected,
+            span,
+        })
+    } else if n.is_comment() {
+        Value::String {
+            val: "<comment>".to_string(),
+            span,
+        }
+    } else if n.is_pi() {
+        Value::String {
+            val: "<processing_instruction>".to_string(),
+            span,
+        }
+    } else if n.is_text() {
+        match n.text() {
+            Some(text) => Value::String {
+                val: text.to_string(),
+                span,
+            },
+            None => Value::String {
+                val: "<error>".to_string(),
+                span,
+            },
+        }
+    } else {
+        Value::String {
+            val: "<unknown>".to_string(),
+            span,
+        }
+    }
+}
+
+fn from_document_to_value(d: &roxmltree::Document, span: Span) -> Value {
+    from_node_to_value(&d.root_element(), span)
+}
+
+pub fn from_xml_string_to_value(s: String, span: Span) -> Result<Value, roxmltree::Error> {
+    let parsed = roxmltree::Document::parse(&s)?;
+    Ok(from_document_to_value(&parsed, span))
+}
+
+fn from_xml(input: PipelineData, head: Span, config: &Config) -> Result<PipelineData, ShellError> {
+    let concat_string = input.collect_string("", config);
+
+    match from_xml_string_to_value(concat_string, head) {
+        Ok(x) => Ok(x.into_pipeline_data()),
+        _ => Err(ShellError::UnsupportedInput(
+            "Could not parse string as xml".to_string(),
+            head,
+        )),
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    use indexmap::indexmap;
+    use indexmap::IndexMap;
+    use nu_protocol::{Spanned, Value};
+
+    fn string(input: impl Into<String>) -> Value {
+        Value::String {
+            val: input.into(),
+            span: Span::unknown(),
+        }
+    }
+
+    fn row(entries: IndexMap<String, Value>) -> Value {
+        Value::from(Spanned {
+            item: entries,
+            span: Span::unknown(),
+        })
+    }
+
+    fn table(list: &[Value]) -> Value {
+        Value::List {
+            vals: list.to_vec(),
+            span: Span::unknown(),
+        }
+    }
+
+    fn parse(xml: &str) -> Result<Value, roxmltree::Error> {
+        from_xml_string_to_value(xml.to_string(), Span::unknown())
+    }
+
+    #[test]
+    fn parses_empty_element() -> Result<(), roxmltree::Error> {
+        let source = "<nu></nu>";
+
+        assert_eq!(
+            parse(source)?,
+            row(indexmap! {
+                "nu".into() => row(indexmap! {
+                    "children".into() => table(&[]),
+                    "attributes".into() => row(indexmap! {})
+                })
+            })
+        );
+
+        Ok(())
+    }
+
+    #[test]
+    fn parses_element_with_text() -> Result<(), roxmltree::Error> {
+        let source = "<nu>La era de los tres caballeros</nu>";
+
+        assert_eq!(
+            parse(source)?,
+            row(indexmap! {
+                "nu".into() => row(indexmap! {
+                    "children".into() => table(&[string("La era de los tres caballeros")]),
+                    "attributes".into() => row(indexmap! {})
+                })
+            })
+        );
+
+        Ok(())
+    }
+
+    #[test]
+    fn parses_element_with_elements() -> Result<(), roxmltree::Error> {
+        let source = "\
+<nu>
+    <dev>Andrés</dev>
+    <dev>Jonathan</dev>
+    <dev>Yehuda</dev>
+</nu>";
+
+        assert_eq!(
+            parse(source)?,
+            row(indexmap! {
+                "nu".into() => row(indexmap! {
+                    "children".into() => table(&[
+                        row(indexmap! {
+                            "dev".into() => row(indexmap! {
+                                "children".into() => table(&[string("Andrés")]),
+                                "attributes".into() => row(indexmap! {})
+                            })
+                        }),
+                        row(indexmap! {
+                            "dev".into() => row(indexmap! {
+                                "children".into() => table(&[string("Jonathan")]),
+                                "attributes".into() => row(indexmap! {})
+                            })
+                        }),
+                        row(indexmap! {
+                            "dev".into() => row(indexmap! {
+                                "children".into() => table(&[string("Yehuda")]),
+                                "attributes".into() => row(indexmap! {})
+                            })
+                        })
+                    ]),
+                    "attributes".into() => row(indexmap! {})
+                })
+            })
+        );
+
+        Ok(())
+    }
+
+    #[test]
+    fn parses_element_with_attribute() -> Result<(), roxmltree::Error> {
+        let source = "\
+<nu version=\"2.0\">
+</nu>";
+
+        assert_eq!(
+            parse(source)?,
+            row(indexmap! {
+                "nu".into() => row(indexmap! {
+                    "children".into() => table(&[]),
+                    "attributes".into() => row(indexmap! {
+                        "version".into() => string("2.0")
+                    })
+                })
+            })
+        );
+
+        Ok(())
+    }
+
+    #[test]
+    fn parses_element_with_attribute_and_element() -> Result<(), roxmltree::Error> {
+        let source = "\
+<nu version=\"2.0\">
+    <version>2.0</version>
+</nu>";
+
+        assert_eq!(
+            parse(source)?,
+            row(indexmap! {
+                "nu".into() => row(indexmap! {
+                    "children".into() => table(&[
+                           row(indexmap! {
+                                "version".into() => row(indexmap! {
+                                    "children".into() => table(&[string("2.0")]),
+                                    "attributes".into() => row(indexmap! {})
+                                })
+                          })
+                    ]),
+                    "attributes".into() => row(indexmap! {
+                        "version".into() => string("2.0")
+                    })
+                })
+            })
+        );
+
+        Ok(())
+    }
+
+    #[test]
+    fn parses_element_with_multiple_attributes() -> Result<(), roxmltree::Error> {
+        let source = "\
+<nu version=\"2.0\" age=\"25\">
+</nu>";
+
+        assert_eq!(
+            parse(source)?,
+            row(indexmap! {
+                "nu".into() => row(indexmap! {
+                    "children".into() => table(&[]),
+                    "attributes".into() => row(indexmap! {
+                        "version".into() => string("2.0"),
+                        "age".into() => string("25")
+                    })
+                })
+            })
+        );
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_examples() {
+        use crate::test_examples;
+
+        test_examples(FromXml {})
+    }
+}
diff --git a/crates/nu-command/src/formats/to/json.rs b/crates/nu-command/src/formats/to/json.rs
index e15b467..8e35048 100644
--- a/crates/nu-command/src/formats/to/json.rs
+++ b/crates/nu-command/src/formats/to/json.rs
@@ -73,7 +73,6 @@ pub fn value_to_json_value(v: &Value) -> Result<nu_json::Value, ShellError> {
             }
             nu_json::Value::Object(m)
         }
-        #[cfg(feature = "custom")]
         Value::CustomValue { val, .. } => val.to_json(),
     })
 }
diff --git a/crates/nu-command/src/lib.rs b/crates/nu-command/src/lib.rs
index 3e8cbee..12b0d9b 100644
--- a/crates/nu-command/src/lib.rs
+++ b/crates/nu-command/src/lib.rs
@@ -10,6 +10,7 @@ mod filters;
 mod formats;
 mod math;
 mod platform;
+mod random;
 mod shells;
 mod strings;
 mod system;
@@ -30,6 +31,7 @@ pub use filters::*;
 pub use formats::*;
 pub use math::*;
 pub use platform::*;
+pub use random::*;
 pub use shells::*;
 pub use strings::*;
 pub use system::*;
diff --git a/crates/nu-command/src/platform/kill.rs b/crates/nu-command/src/platform/kill.rs
new file mode 100644
index 0000000..9ab4f07
--- /dev/null
+++ b/crates/nu-command/src/platform/kill.rs
@@ -0,0 +1,149 @@
+use nu_engine::CallExt;
+use nu_protocol::engine::{Command, EngineState, Stack};
+use nu_protocol::{ast::Call, span};
+use nu_protocol::{
+    Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Spanned, SyntaxShape,
+    Value,
+};
+use std::process::{Command as CommandSys, Stdio};
+
+#[derive(Clone)]
+pub struct Kill;
+
+impl Command for Kill {
+    fn name(&self) -> &str {
+        "kill"
+    }
+
+    fn usage(&self) -> &str {
+        "Kill a process using the process id."
+    }
+
+    fn signature(&self) -> Signature {
+        let signature = Signature::build("kill")
+            .required(
+                "pid",
+                SyntaxShape::Int,
+                "process id of process that is to be killed",
+            )
+            .rest("rest", SyntaxShape::Int, "rest of processes to kill")
+            .switch("force", "forcefully kill the process", Some('f'))
+            .switch("quiet", "won't print anything to the console", Some('q'))
+            .category(Category::Platform);
+
+        if cfg!(windows) {
+            return signature;
+        }
+
+        signature.named(
+            "signal",
+            SyntaxShape::Int,
+            "signal decimal number to be sent instead of the default 15 (unsupported on Windows)",
+            Some('s'),
+        )
+    }
+
+    fn run(
+        &self,
+        engine_state: &EngineState,
+        stack: &mut Stack,
+        call: &Call,
+        _input: PipelineData,
+    ) -> Result<PipelineData, ShellError> {
+        let pid: i64 = call.req(engine_state, stack, 0)?;
+        let rest: Vec<i64> = call.rest(engine_state, stack, 1)?;
+        let force: bool = call.has_flag("force");
+        let signal: Option<Spanned<i64>> = call.get_flag(engine_state, stack, "signal")?;
+        let quiet: bool = call.has_flag("quiet");
+
+        let mut cmd = if cfg!(windows) {
+            let mut cmd = CommandSys::new("taskkill");
+
+            if force {
+                cmd.arg("/F");
+            }
+
+            cmd.arg("/PID");
+            cmd.arg(pid.to_string());
+
+            // each pid must written as `/PID 0` otherwise
+            // taskkill will act as `killall` unix command
+            for id in &rest {
+                cmd.arg("/PID");
+                cmd.arg(id.to_string());
+            }
+
+            cmd
+        } else {
+            let mut cmd = CommandSys::new("kill");
+            if force {
+                if let Some(Spanned {
+                    item: _,
+                    span: signal_span,
+                }) = signal
+                {
+                    return Err(ShellError::IncompatibleParameters {
+                        left_message: "force".to_string(),
+                        left_span: call.get_named_arg("force").unwrap().span,
+                        right_message: "signal".to_string(),
+                        right_span: span(&[
+                            call.get_named_arg("signal").unwrap().span,
+                            signal_span,
+                        ]),
+                    });
+                }
+                cmd.arg("-9");
+            } else if let Some(signal_value) = signal {
+                cmd.arg(format!("-{}", signal_value.item.to_string()));
+            }
+
+            cmd.arg(pid.to_string());
+
+            cmd.args(rest.iter().map(move |id| id.to_string()));
+
+            cmd
+        };
+
+        // pipe everything to null
+        if quiet {
+            cmd.stdin(Stdio::null())
+                .stdout(Stdio::null())
+                .stderr(Stdio::null());
+        }
+
+        cmd.status().expect("failed to execute shell command");
+
+        Ok(Value::Nothing { span: call.head }.into_pipeline_data())
+    }
+
+    fn examples(&self) -> Vec<Example> {
+        vec![
+            Example {
+                description: "Kill the pid using the most memory",
+                example: "ps | sort-by mem | last | kill $it.pid",
+                result: None,
+            },
+            Example {
+                description: "Force kill a given pid",
+                example: "kill --force 12345",
+                result: None,
+            },
+            Example {
+                description: "Send INT signal",
+                example: "kill -s 2 12345",
+                result: None,
+            },
+        ]
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::Kill;
+
+    #[test]
+    fn examples_work_as_expected() {
+        use crate::test_examples;
+        test_examples(Kill {})
+    }
+}
diff --git a/crates/nu-command/src/platform/mod.rs b/crates/nu-command/src/platform/mod.rs
index 0d2cdbd..bb50c30 100644
--- a/crates/nu-command/src/platform/mod.rs
+++ b/crates/nu-command/src/platform/mod.rs
@@ -1,3 +1,7 @@
 mod clear;
+mod kill;
+mod sleep;
 
 pub use clear::Clear;
+pub use kill::Kill;
+pub use sleep::Sleep;
diff --git a/crates/nu-command/src/platform/sleep.rs b/crates/nu-command/src/platform/sleep.rs
new file mode 100644
index 0000000..9591de7
--- /dev/null
+++ b/crates/nu-command/src/platform/sleep.rs
@@ -0,0 +1,108 @@
+use nu_engine::CallExt;
+use nu_protocol::ast::Call;
+use nu_protocol::engine::{Command, EngineState, Stack};
+use nu_protocol::{
+    Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, SyntaxShape, Value,
+};
+use std::{
+    sync::atomic::Ordering,
+    thread,
+    time::{Duration, Instant},
+};
+
+const CTRL_C_CHECK_INTERVAL: Duration = Duration::from_millis(100);
+
+#[derive(Clone)]
+pub struct Sleep;
+
+impl Command for Sleep {
+    fn name(&self) -> &str {
+        "sleep"
+    }
+
+    fn usage(&self) -> &str {
+        "Delay for a specified amount of time."
+    }
+
+    fn signature(&self) -> Signature {
+        Signature::build("sleep")
+            .required("duration", SyntaxShape::Duration, "time to sleep")
+            .rest("rest", SyntaxShape::Duration, "additional time")
+            .category(Category::Platform)
+    }
+
+    fn run(
+        &self,
+        engine_state: &EngineState,
+        stack: &mut Stack,
+        call: &Call,
+        _input: PipelineData,
+    ) -> Result<PipelineData, ShellError> {
+        fn duration_from_i64(val: i64) -> Duration {
+            Duration::from_nanos(if val < 0 { 0 } else { val as u64 })
+        }
+
+        let duration: i64 = call.req(engine_state, stack, 0)?;
+        let rest: Vec<i64> = call.rest(engine_state, stack, 1)?;
+
+        let total_dur =
+            duration_from_i64(duration) + rest.into_iter().map(duration_from_i64).sum::<Duration>();
+
+        let ctrlc_ref = &engine_state.ctrlc.clone();
+        let start = Instant::now();
+        loop {
+            thread::sleep(CTRL_C_CHECK_INTERVAL);
+            if start.elapsed() >= total_dur {
+                break;
+            }
+
+            if let Some(ctrlc) = ctrlc_ref {
+                if ctrlc.load(Ordering::SeqCst) {
+                    break;
+                }
+            }
+        }
+
+        Ok(Value::Nothing { span: call.head }.into_pipeline_data())
+    }
+
+    fn examples(&self) -> Vec<Example> {
+        vec![
+            Example {
+                description: "Sleep for 1sec",
+                example: "sleep 1sec",
+                result: None,
+            },
+            Example {
+                description: "Sleep for 3sec",
+                example: "sleep 1sec 1sec 1sec",
+                result: None,
+            },
+            Example {
+                description: "Send output after 1sec",
+                example: "sleep 1sec; echo done",
+                result: Some(Value::test_string("done")),
+            },
+        ]
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::Sleep;
+
+    #[test]
+    fn examples_work_as_expected() {
+        use crate::test_examples;
+        use std::time::Instant;
+
+        let start = Instant::now();
+        test_examples(Sleep {});
+
+        let elapsed = start.elapsed();
+
+        // only examples with actual output are run
+        assert!(elapsed >= std::time::Duration::from_secs(1));
+        assert!(elapsed < std::time::Duration::from_secs(2));
+    }
+}
diff --git a/crates/nu-command/src/random/bool.rs b/crates/nu-command/src/random/bool.rs
new file mode 100644
index 0000000..bd462d5
--- /dev/null
+++ b/crates/nu-command/src/random/bool.rs
@@ -0,0 +1,97 @@
+use nu_engine::CallExt;
+use nu_protocol::ast::Call;
+use nu_protocol::engine::{Command, EngineState, Stack};
+use nu_protocol::{
+    Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Value,
+};
+use rand::prelude::{thread_rng, Rng};
+
+#[derive(Clone)]
+pub struct SubCommand;
+
+impl Command for SubCommand {
+    fn name(&self) -> &str {
+        "random bool"
+    }
+
+    fn signature(&self) -> Signature {
+        Signature::build("random bool")
+            .named(
+                "bias",
+                SyntaxShape::Number,
+                "Adjusts the probability of a \"true\" outcome",
+                Some('b'),
+            )
+            .category(Category::Random)
+    }
+
+    fn usage(&self) -> &str {
+        "Generate a random boolean value"
+    }
+
+    fn run(
+        &self,
+        engine_state: &EngineState,
+        stack: &mut Stack,
+        call: &Call,
+        _input: PipelineData,
+    ) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
+        bool(engine_state, stack, call)
+    }
+
+    fn examples(&self) -> Vec<Example> {
+        vec![
+            Example {
+                description: "Generate a random boolean value",
+                example: "random bool",
+                result: None,
+            },
+            Example {
+                description: "Generate a random boolean value with a 75% chance of \"true\"",
+                example: "random bool --bias 0.75",
+                result: None,
+            },
+        ]
+    }
+}
+
+fn bool(
+    engine_state: &EngineState,
+    stack: &mut Stack,
+    call: &Call,
+) -> Result<PipelineData, ShellError> {
+    let span = call.head;
+    let bias: Option<Spanned<f64>> = call.get_flag(engine_state, stack, "bias")?;
+
+    let mut probability = 0.5;
+
+    if let Some(prob) = bias {
+        probability = prob.item;
+
+        let probability_is_valid = (0.0..=1.0).contains(&probability);
+
+        if !probability_is_valid {
+            return Err(ShellError::InvalidProbability(prob.span));
+        }
+    }
+
+    let mut rng = thread_rng();
+    let bool_result: bool = rng.gen_bool(probability);
+
+    Ok(PipelineData::Value(Value::Bool {
+        val: bool_result,
+        span,
+    }))
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    #[test]
+    fn test_examples() {
+        use crate::test_examples;
+
+        test_examples(SubCommand {})
+    }
+}
diff --git a/crates/nu-command/src/random/command.rs b/crates/nu-command/src/random/command.rs
new file mode 100644
index 0000000..f7d0ece
--- /dev/null
+++ b/crates/nu-command/src/random/command.rs
@@ -0,0 +1,41 @@
+use nu_engine::get_full_help;
+use nu_protocol::{
+    ast::Call,
+    engine::{Command, EngineState, Stack},
+    Category, IntoPipelineData, PipelineData, Signature, Value,
+};
+
+#[derive(Clone)]
+pub struct RandomCommand;
+
+impl Command for RandomCommand {
+    fn name(&self) -> &str {
+        "random"
+    }
+
+    fn signature(&self) -> Signature {
+        Signature::build("random").category(Category::Random)
+    }
+
+    fn usage(&self) -> &str {
+        "Generate a random values."
+    }
+
+    fn run(
+        &self,
+        engine_state: &EngineState,
+        _stack: &mut Stack,
+        call: &Call,
+        _input: PipelineData,
+    ) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
+        Ok(Value::String {
+            val: get_full_help(
+                &RandomCommand.signature(),
+                &RandomCommand.examples(),
+                engine_state,
+            ),
+            span: call.head,
+        }
+        .into_pipeline_data())
+    }
+}
diff --git a/crates/nu-command/src/random/mod.rs b/crates/nu-command/src/random/mod.rs
new file mode 100644
index 0000000..1e3cdf0
--- /dev/null
+++ b/crates/nu-command/src/random/mod.rs
@@ -0,0 +1,5 @@
+mod bool;
+mod command;
+
+pub use self::bool::SubCommand as Bool;
+pub use command::RandomCommand as Random;
diff --git a/crates/nu-command/src/viewers/griddle.rs b/crates/nu-command/src/viewers/griddle.rs
index 6ad5c28..a04457f 100644
--- a/crates/nu-command/src/viewers/griddle.rs
+++ b/crates/nu-command/src/viewers/griddle.rs
@@ -1,3 +1,5 @@
+// use super::icons::{icon_for_file, iconify_style_ansi_to_nu};
+use super::icons::icon_for_file;
 use lscolors::{LsColors, Style};
 use nu_engine::CallExt;
 use nu_protocol::{
@@ -57,23 +59,23 @@ prints out the list properly."#
         let width_param: Option<String> = call.get_flag(engine_state, stack, "width")?;
         let color_param: bool = call.has_flag("color");
         let separator_param: Option<String> = call.get_flag(engine_state, stack, "separator")?;
-
         let config = stack.get_config()?;
-
         let env_str = stack.get_env_var("LS_COLORS");
+        let use_grid_icons = config.use_grid_icons;
 
         match input {
             PipelineData::Value(Value::List { vals, .. }) => {
                 // dbg!("value::list");
-                let data = convert_to_list2(vals, &config);
+                let data = convert_to_list(vals, &config);
                 if let Some(items) = data {
-                    Ok(create_grid_output2(
+                    Ok(create_grid_output(
                         items,
                         call,
                         width_param,
                         color_param,
                         separator_param,
                         env_str,
+                        use_grid_icons,
                     ))
                 } else {
                     Ok(PipelineData::new(call.head))
@@ -81,15 +83,16 @@ prints out the list properly."#
             }
             PipelineData::Stream(stream) => {
                 // dbg!("value::stream");
-                let data = convert_to_list2(stream, &config);
+                let data = convert_to_list(stream, &config);
                 if let Some(items) = data {
-                    Ok(create_grid_output2(
+                    Ok(create_grid_output(
                         items,
                         call,
                         width_param,
                         color_param,
                         separator_param,
                         env_str,
+                        use_grid_icons,
                     ))
                 } else {
                     // dbg!(data);
@@ -104,13 +107,14 @@ prints out the list properly."#
                     items.push((i, c, v.into_string(", ", &config)))
                 }
 
-                Ok(create_grid_output2(
+                Ok(create_grid_output(
                     items,
                     call,
                     width_param,
                     color_param,
                     separator_param,
                     env_str,
+                    use_grid_icons,
                 ))
             }
             x => {
@@ -122,13 +126,22 @@ prints out the list properly."#
     }
 }
 
-fn create_grid_output2(
+fn strip_ansi(astring: &str) -> String {
+    if let Ok(bytes) = strip_ansi_escapes::strip(astring) {
+        String::from_utf8_lossy(&bytes).to_string()
+    } else {
+        astring.to_string()
+    }
+}
+
+fn create_grid_output(
     items: Vec<(usize, String, String)>,
     call: &Call,
     width_param: Option<String>,
     color_param: bool,
     separator_param: Option<String>,
     env_str: Option<String>,
+    use_grid_icons: bool,
 ) -> PipelineData {
     let ls_colors = match env_str {
         Some(s) => LsColors::from_string(&s),
@@ -157,14 +170,43 @@ fn create_grid_output2(
         // only output value if the header name is 'name'
         if header == "name" {
             if color_param {
-                let style = ls_colors.style_for_path(value.clone());
-                let ansi_style = style.map(Style::to_crossterm_style).unwrap_or_default();
-                let mut cell = Cell::from(ansi_style.apply(value).to_string());
-                cell.alignment = Alignment::Right;
-                grid.add(cell);
+                if use_grid_icons {
+                    let no_ansi = strip_ansi(&value);
+                    let path = std::path::Path::new(&no_ansi);
+                    let icon = icon_for_file(path);
+                    let ls_colors_style = ls_colors.style_for_path(path);
+                    // eprintln!("ls_colors_style: {:?}", &ls_colors_style);
+
+                    let icon_style = match ls_colors_style {
+                        Some(c) => c.to_crossterm_style(),
+                        None => crossterm::style::ContentStyle::default(),
+                    };
+                    // eprintln!("icon_style: {:?}", &icon_style);
+
+                    let ansi_style = ls_colors_style
+                        .map(Style::to_crossterm_style)
+                        .unwrap_or_default();
+                    // eprintln!("ansi_style: {:?}", &ansi_style);
+
+                    let item = format!(
+                        "{} {}",
+                        icon_style.apply(icon).to_string(),
+                        ansi_style.apply(value).to_string()
+                    );
+
+                    let mut cell = Cell::from(item);
+                    cell.alignment = Alignment::Left;
+                    grid.add(cell);
+                } else {
+                    let style = ls_colors.style_for_path(value.clone());
+                    let ansi_style = style.map(Style::to_crossterm_style).unwrap_or_default();
+                    let mut cell = Cell::from(ansi_style.apply(value).to_string());
+                    cell.alignment = Alignment::Left;
+                    grid.add(cell);
+                }
             } else {
                 let mut cell = Cell::from(value);
-                cell.alignment = Alignment::Right;
+                cell.alignment = Alignment::Left;
                 grid.add(cell);
             }
         }
@@ -184,7 +226,7 @@ fn create_grid_output2(
     .into_pipeline_data()
 }
 
-fn convert_to_list2(
+fn convert_to_list(
     iter: impl IntoIterator<Item = Value>,
     config: &Config,
 ) -> Option<Vec<(usize, String, String)>> {
diff --git a/crates/nu-command/src/viewers/icons.rs b/crates/nu-command/src/viewers/icons.rs
new file mode 100644
index 0000000..e504e10
--- /dev/null
+++ b/crates/nu-command/src/viewers/icons.rs
@@ -0,0 +1,580 @@
+use lazy_static::lazy_static;
+use std::collections::HashMap;
+use std::path::Path;
+
+// Attribution: Thanks exa. Most of this file is taken from around here
+// https://github.com/ogham/exa/blob/dbd11d38042284cc890fdd91760c2f93b65e8553/src/output/icons.rs
+
+pub trait FileIcon {
+    fn icon_file(&self, file: &Path) -> Option<char>;
+}
+
+#[derive(Copy, Clone)]
+pub enum Icons {
+    Audio,
+    Image,
+    Video,
+}
+
+impl Icons {
+    pub fn value(self) -> char {
+        match self {
+            Self::Audio => '\u{f001}',
+            Self::Image => '\u{f1c5}',
+            Self::Video => '\u{f03d}',
+        }
+    }
+}
+
+// keeping this for now in case we have to revert to ansi style instead of crossterm style
+// Helper function to convert ansi_term style to nu_ansi_term. unfortunately
+// this is necessary because ls_colors has a dependency on ansi_term vs nu_ansi_term
+// double unfortunately, now we have a dependency on both. we may have to bring
+// in ls_colors crate to nushell
+// pub fn iconify_style_ansi_to_nu<'a>(style: ansi_term::Style) -> nu_ansi_term::Style {
+//     let bg = match style.background {
+//         Some(c) => match c {
+//             ansi_term::Color::Black => Some(nu_ansi_term::Color::Black),
+//             ansi_term::Color::Red => Some(nu_ansi_term::Color::Red),
+//             ansi_term::Color::Green => Some(nu_ansi_term::Color::Green),
+//             ansi_term::Color::Yellow => Some(nu_ansi_term::Color::Yellow),
+//             ansi_term::Color::Blue => Some(nu_ansi_term::Color::Blue),
+//             ansi_term::Color::Purple => Some(nu_ansi_term::Color::Purple),
+//             ansi_term::Color::Cyan => Some(nu_ansi_term::Color::Cyan),
+//             ansi_term::Color::White => Some(nu_ansi_term::Color::White),
+//             ansi_term::Color::Fixed(f) => Some(nu_ansi_term::Color::Fixed(f)),
+//             ansi_term::Color::RGB(r, g, b) => Some(nu_ansi_term::Color::Rgb(r, g, b)),
+//         },
+//         None => None,
+//     };
+
+//     let fg = match style.foreground {
+//         Some(c) => match c {
+//             ansi_term::Color::Black => Some(nu_ansi_term::Color::Black),
+//             ansi_term::Color::Red => Some(nu_ansi_term::Color::Red),
+//             ansi_term::Color::Green => Some(nu_ansi_term::Color::Green),
+//             ansi_term::Color::Yellow => Some(nu_ansi_term::Color::Yellow),
+//             ansi_term::Color::Blue => Some(nu_ansi_term::Color::Blue),
+//             ansi_term::Color::Purple => Some(nu_ansi_term::Color::Purple),
+//             ansi_term::Color::Cyan => Some(nu_ansi_term::Color::Cyan),
+//             ansi_term::Color::White => Some(nu_ansi_term::Color::White),
+//             ansi_term::Color::Fixed(f) => Some(nu_ansi_term::Color::Fixed(f)),
+//             ansi_term::Color::RGB(r, g, b) => Some(nu_ansi_term::Color::Rgb(r, g, b)),
+//         },
+//         None => None,
+//     };
+
+//     let nu_style = nu_ansi_term::Style {
+//         foreground: fg,
+//         background: bg,
+//         is_blink: style.is_blink,
+//         is_bold: style.is_bold,
+//         is_dimmed: style.is_dimmed,
+//         is_hidden: style.is_hidden,
+//         is_italic: style.is_italic,
+//         is_underline: style.is_underline,
+//         is_reverse: style.is_reverse,
+//         is_strikethrough: style.is_strikethrough,
+//     };
+
+//     nu_style
+//         .background
+//         .or(nu_style.foreground)
+//         .map(nu_ansi_term::Style::from)
+//         .unwrap_or_default()
+// }
+
+lazy_static! {
+    static ref MAP_BY_NAME: HashMap<&'static str, char> = {
+        let mut m = HashMap::new();
+        m.insert(".Trash", '\u{f1f8}'); // 
+        m.insert(".atom", '\u{e764}'); // 
+        m.insert(".bashprofile", '\u{e615}'); // 
+        m.insert(".bashrc", '\u{f489}'); // 
+        m.insert(".git", '\u{f1d3}'); // 
+        m.insert(".gitattributes", '\u{f1d3}'); // 
+        m.insert(".gitconfig", '\u{f1d3}'); // 
+        m.insert(".github", '\u{f408}'); // 
+        m.insert(".gitignore", '\u{f1d3}'); // 
+        m.insert(".gitmodules", '\u{f1d3}'); // 
+        m.insert(".rvm", '\u{e21e}'); // 
+        m.insert(".vimrc", '\u{e62b}'); // 
+        m.insert(".vscode", '\u{e70c}'); // 
+        m.insert(".zshrc", '\u{f489}'); // 
+        m.insert("Cargo.lock", '\u{e7a8}'); // 
+        m.insert("bin", '\u{e5fc}'); // 
+        m.insert("config", '\u{e5fc}'); // 
+        m.insert("docker-compose.yml", '\u{f308}'); // 
+        m.insert("Dockerfile", '\u{f308}'); // 
+        m.insert("ds_store", '\u{f179}'); // 
+        m.insert("gitignore_global", '\u{f1d3}'); // 
+        m.insert("gradle", '\u{e70e}'); // 
+        m.insert("gruntfile.coffee", '\u{e611}'); // 
+        m.insert("gruntfile.js", '\u{e611}'); // 
+        m.insert("gruntfile.ls", '\u{e611}'); // 
+        m.insert("gulpfile.coffee", '\u{e610}'); // 
+        m.insert("gulpfile.js", '\u{e610}'); // 
+        m.insert("gulpfile.ls", '\u{e610}'); // 
+        m.insert("hidden", '\u{f023}'); // 
+        m.insert("include", '\u{e5fc}'); // 
+        m.insert("lib", '\u{f121}'); // 
+        m.insert("localized", '\u{f179}'); // 
+        m.insert("Makefile", '\u{e779}'); // 
+        m.insert("node_modules", '\u{e718}'); // 
+        m.insert("npmignore", '\u{e71e}'); // 
+        m.insert("rubydoc", '\u{e73b}'); // 
+        m.insert("yarn.lock", '\u{e718}'); // 
+
+        m
+    };
+}
+
+pub fn icon_for_file(file_path: &Path) -> char {
+    let extensions = Box::new(FileExtensions);
+    let fp = format!("{}", file_path.display());
+
+    if let Some(icon) = MAP_BY_NAME.get(&fp[..]) {
+        *icon
+    } else if file_path.is_dir() {
+        match file_path.file_name().unwrap().to_str().unwrap() {
+            "bin" => '\u{e5fc}',   // 
+            ".git" => '\u{f1d3}',  // 
+            ".idea" => '\u{e7b5}', // 
+            _ => '\u{f115}',       // 
+        }
+    } else if let Some(icon) = extensions.icon_file(file_path) {
+        icon
+    } else if let Some(ext) = file_path.extension().as_ref() {
+        match ext.to_str().unwrap() {
+            "ai" => '\u{e7b4}',             // 
+            "android" => '\u{e70e}',        // 
+            "apk" => '\u{e70e}',            // 
+            "apple" => '\u{f179}',          // 
+            "avi" => '\u{f03d}',            // 
+            "avro" => '\u{e60b}',           // 
+            "awk" => '\u{f489}',            // 
+            "bash" => '\u{f489}',           // 
+            "bash_history" => '\u{f489}',   // 
+            "bash_profile" => '\u{f489}',   // 
+            "bashrc" => '\u{f489}',         // 
+            "bat" => '\u{f17a}',            // 
+            "bmp" => '\u{f1c5}',            // 
+            "bz" => '\u{f410}',             // 
+            "bz2" => '\u{f410}',            // 
+            "c" => '\u{e61e}',              // 
+            "c++" => '\u{e61d}',            // 
+            "cab" => '\u{e70f}',            // 
+            "cc" => '\u{e61d}',             // 
+            "cfg" => '\u{e615}',            // 
+            "class" => '\u{e256}',          // 
+            "clj" => '\u{e768}',            // 
+            "cljs" => '\u{e76a}',           // 
+            "cls" => '\u{e600}',            // 
+            "cmd" => '\u{e70f}',            // 
+            "coffee" => '\u{f0f4}',         // 
+            "conf" => '\u{e615}',           // 
+            "cp" => '\u{e61d}',             // 
+            "cpp" => '\u{e61d}',            // 
+            "cs" => '\u{f81a}',             // 
+            "csh" => '\u{f489}',            // 
+            "cshtml" => '\u{f1fa}',         // 
+            "csproj" => '\u{f81a}',         // 
+            "css" => '\u{e749}',            // 
+            "csv" => '\u{f1c3}',            // 
+            "csx" => '\u{f81a}',            // 
+            "cxx" => '\u{e61d}',            // 
+            "d" => '\u{e7af}',              // 
+            "dart" => '\u{e798}',           // 
+            "db" => '\u{f1c0}',             // 
+            "deb" => '\u{e77d}',            // 
+            "diff" => '\u{f440}',           // 
+            "djvu" => '\u{f02d}',           // 
+            "dll" => '\u{e70f}',            // 
+            "doc" => '\u{f1c2}',            // 
+            "docx" => '\u{f1c2}',           // 
+            "ds_store" => '\u{f179}',       // 
+            "DS_store" => '\u{f179}',       // 
+            "dump" => '\u{f1c0}',           // 
+            "ebook" => '\u{e28b}',          // 
+            "editorconfig" => '\u{e615}',   // 
+            "ejs" => '\u{e618}',            // 
+            "elm" => '\u{e62c}',            // 
+            "env" => '\u{f462}',            // 
+            "eot" => '\u{f031}',            // 
+            "epub" => '\u{e28a}',           // 
+            "erb" => '\u{e73b}',            // 
+            "erl" => '\u{e7b1}',            // 
+            "ex" => '\u{e62d}',             // 
+            "exe" => '\u{f17a}',            // 
+            "exs" => '\u{e62d}',            // 
+            "fish" => '\u{f489}',           // 
+            "flac" => '\u{f001}',           // 
+            "flv" => '\u{f03d}',            // 
+            "font" => '\u{f031}',           // 
+            "gdoc" => '\u{f1c2}',           // 
+            "gem" => '\u{e21e}',            // 
+            "gemfile" => '\u{e21e}',        // 
+            "gemspec" => '\u{e21e}',        // 
+            "gform" => '\u{f298}',          // 
+            "gif" => '\u{f1c5}',            // 
+            "git" => '\u{f1d3}',            // 
+            "gitattributes" => '\u{f1d3}',  // 
+            "gitignore" => '\u{f1d3}',      // 
+            "gitmodules" => '\u{f1d3}',     // 
+            "go" => '\u{e626}',             // 
+            "gradle" => '\u{e70e}',         // 
+            "groovy" => '\u{e775}',         // 
+            "gsheet" => '\u{f1c3}',         // 
+            "gslides" => '\u{f1c4}',        // 
+            "guardfile" => '\u{e21e}',      // 
+            "gz" => '\u{f410}',             // 
+            "h" => '\u{f0fd}',              // 
+            "hbs" => '\u{e60f}',            // 
+            "hpp" => '\u{f0fd}',            // 
+            "hs" => '\u{e777}',             // 
+            "htm" => '\u{f13b}',            // 
+            "html" => '\u{f13b}',           // 
+            "hxx" => '\u{f0fd}',            // 
+            "ico" => '\u{f1c5}',            // 
+            "image" => '\u{f1c5}',          // 
+            "iml" => '\u{e7b5}',            // 
+            "ini" => '\u{f17a}',            // 
+            "ipynb" => '\u{e606}',          // 
+            "iso" => '\u{e271}',            // 
+            "jad" => '\u{e256}',            // 
+            "jar" => '\u{e204}',            // 
+            "java" => '\u{e204}',           // 
+            "jpeg" => '\u{f1c5}',           // 
+            "jpg" => '\u{f1c5}',            // 
+            "js" => '\u{e74e}',             // 
+            "json" => '\u{e60b}',           // 
+            "jsx" => '\u{e7ba}',            // 
+            "ksh" => '\u{f489}',            // 
+            "latex" => '\u{e600}',          // 
+            "less" => '\u{e758}',           // 
+            "lhs" => '\u{e777}',            // 
+            "license" => '\u{f718}',        // 
+            "localized" => '\u{f179}',      // 
+            "lock" => '\u{f023}',           // 
+            "log" => '\u{f18d}',            // 
+            "lua" => '\u{e620}',            // 
+            "lz" => '\u{f410}',             // 
+            "lzh" => '\u{f410}',            // 
+            "lzma" => '\u{f410}',           // 
+            "lzo" => '\u{f410}',            // 
+            "m" => '\u{e61e}',              // 
+            "mm" => '\u{e61d}',             // 
+            "m4a" => '\u{f001}',            // 
+            "markdown" => '\u{f48a}',       // 
+            "md" => '\u{f48a}',             // 
+            "mjs" => '\u{e74e}',            // 
+            "mkd" => '\u{f48a}',            // 
+            "mkv" => '\u{f03d}',            // 
+            "mobi" => '\u{e28b}',           // 
+            "mov" => '\u{f03d}',            // 
+            "mp3" => '\u{f001}',            // 
+            "mp4" => '\u{f03d}',            // 
+            "msi" => '\u{e70f}',            // 
+            "mustache" => '\u{e60f}',       // 
+            "nix" => '\u{f313}',            // 
+            "node" => '\u{f898}',           // 
+            "npmignore" => '\u{e71e}',      // 
+            "odp" => '\u{f1c4}',            // 
+            "ods" => '\u{f1c3}',            // 
+            "odt" => '\u{f1c2}',            // 
+            "ogg" => '\u{f001}',            // 
+            "ogv" => '\u{f03d}',            // 
+            "otf" => '\u{f031}',            // 
+            "patch" => '\u{f440}',          // 
+            "pdf" => '\u{f1c1}',            // 
+            "php" => '\u{e73d}',            // 
+            "pl" => '\u{e769}',             // 
+            "png" => '\u{f1c5}',            // 
+            "ppt" => '\u{f1c4}',            // 
+            "pptx" => '\u{f1c4}',           // 
+            "procfile" => '\u{e21e}',       // 
+            "properties" => '\u{e60b}',     // 
+            "ps1" => '\u{f489}',            // 
+            "psd" => '\u{e7b8}',            // 
+            "pxm" => '\u{f1c5}',            // 
+            "py" => '\u{e606}',             // 
+            "pyc" => '\u{e606}',            // 
+            "r" => '\u{f25d}',              // 
+            "rakefile" => '\u{e21e}',       // 
+            "rar" => '\u{f410}',            // 
+            "razor" => '\u{f1fa}',          // 
+            "rb" => '\u{e21e}',             // 
+            "rdata" => '\u{f25d}',          // 
+            "rdb" => '\u{e76d}',            // 
+            "rdoc" => '\u{f48a}',           // 
+            "rds" => '\u{f25d}',            // 
+            "readme" => '\u{f48a}',         // 
+            "rlib" => '\u{e7a8}',           // 
+            "rmd" => '\u{f48a}',            // 
+            "rpm" => '\u{e7bb}',            // 
+            "rs" => '\u{e7a8}',             // 
+            "rspec" => '\u{e21e}',          // 
+            "rspec_parallel" => '\u{e21e}', // 
+            "rspec_status" => '\u{e21e}',   // 
+            "rss" => '\u{f09e}',            // 
+            "rtf" => '\u{f718}',            // 
+            "ru" => '\u{e21e}',             // 
+            "rubydoc" => '\u{e73b}',        // 
+            "sass" => '\u{e603}',           // 
+            "scala" => '\u{e737}',          // 
+            "scss" => '\u{e749}',           // 
+            "sh" => '\u{f489}',             // 
+            "shell" => '\u{f489}',          // 
+            "slim" => '\u{e73b}',           // 
+            "sln" => '\u{e70c}',            // 
+            "so" => '\u{f17c}',             // 
+            "sql" => '\u{f1c0}',            // 
+            "sqlite3" => '\u{e7c4}',        // 
+            "styl" => '\u{e600}',           // 
+            "stylus" => '\u{e600}',         // 
+            "svg" => '\u{f1c5}',            // 
+            "swift" => '\u{e755}',          // 
+            "tar" => '\u{f410}',            // 
+            "taz" => '\u{f410}',            // 
+            "tbz" => '\u{f410}',            // 
+            "tbz2" => '\u{f410}',           // 
+            "tex" => '\u{e600}',            // 
+            "tiff" => '\u{f1c5}',           // 
+            "toml" => '\u{e615}',           // 
+            "ts" => '\u{e628}',             // 
+            "tsv" => '\u{f1c3}',            // 
+            "tsx" => '\u{e7ba}',            // 
+            "ttf" => '\u{f031}',            // 
+            "twig" => '\u{e61c}',           // 
+            "txt" => '\u{f15c}',            // 
+            "tz" => '\u{f410}',             // 
+            "tzo" => '\u{f410}',            // 
+            "video" => '\u{f03d}',          // 
+            "vim" => '\u{e62b}',            // 
+            "vue" => '\u{fd42}',            // ﵂
+            "war" => '\u{e256}',            // 
+            "wav" => '\u{f001}',            // 
+            "webm" => '\u{f03d}',           // 
+            "webp" => '\u{f1c5}',           // 
+            "windows" => '\u{f17a}',        // 
+            "woff" => '\u{f031}',           // 
+            "woff2" => '\u{f031}',          // 
+            "xhtml" => '\u{f13b}',          // 
+            "xls" => '\u{f1c3}',            // 
+            "xlsx" => '\u{f1c3}',           // 
+            "xml" => '\u{fabf}',            // 謹
+            "xul" => '\u{fabf}',            // 謹
+            "xz" => '\u{f410}',             // 
+            "yaml" => '\u{f481}',           // 
+            "yml" => '\u{f481}',            // 
+            "zip" => '\u{f410}',            // 
+            "zsh" => '\u{f489}',            // 
+            "zsh-theme" => '\u{f489}',      // 
+            "zshrc" => '\u{f489}',          // 
+            _ => '\u{f15b}',                // 
+        }
+    } else {
+        '\u{f016}'
+    }
+}
+
+/// Whether this file’s extension is any of the strings that get passed in.
+///
+/// This will always return `false` if the file has no extension.
+pub fn extension_is_one_of(path: &Path, choices: &[&str]) -> bool {
+    match path.extension() {
+        Some(os_ext) => match os_ext.to_str() {
+            Some(ext) => choices.contains(&ext),
+            None => false,
+        },
+        None => false,
+    }
+}
+
+/// Whether this file’s name, including extension, is any of the strings
+/// that get passed in.
+// pub fn name_is_one_of(name: &str, choices: &[&str]) -> bool {
+//     choices.contains(&&name[..])
+// }
+
+#[derive(Debug, Default, PartialEq)]
+pub struct FileExtensions;
+
+// TODO: We may want to re-add these FileExtensions impl fns back. I have disabled
+// it now because it's hard coding colors which kind of defeats the LS_COLORS
+// functionality. We may want to enable and augment at some point.
+
+impl FileExtensions {
+    //     /// An “immediate” file is something that can be run or activated somehow
+    //     /// in order to kick off the build of a project. It’s usually only present
+    //     /// in directories full of source code.
+    //     #[allow(clippy::case_sensitive_file_extension_comparisons)]
+    //     #[allow(dead_code)]
+    //     fn is_immediate(&self, file_path: &Path) -> bool {
+    //         file_path
+    //             .file_name()
+    //             .unwrap()
+    //             .to_str()
+    //             .unwrap()
+    //             .to_lowercase()
+    //             .starts_with("readme")
+    //             || file_path
+    //                 .file_name()
+    //                 .unwrap()
+    //                 .to_str()
+    //                 .unwrap()
+    //                 .ends_with(".ninja")
+    //             || name_is_one_of(
+    //                 file_path.file_name().unwrap().to_str().unwrap(),
+    //                 &[
+    //                     "Makefile",
+    //                     "Cargo.toml",
+    //                     "SConstruct",
+    //                     "CMakeLists.txt",
+    //                     "build.gradle",
+    //                     "pom.xml",
+    //                     "Rakefile",
+    //                     "package.json",
+    //                     "Gruntfile.js",
+    //                     "Gruntfile.coffee",
+    //                     "BUILD",
+    //                     "BUILD.bazel",
+    //                     "WORKSPACE",
+    //                     "build.xml",
+    //                     "Podfile",
+    //                     "webpack.config.js",
+    //                     "meson.build",
+    //                     "composer.json",
+    //                     "RoboFile.php",
+    //                     "PKGBUILD",
+    //                     "Justfile",
+    //                     "Procfile",
+    //                     "Dockerfile",
+    //                     "Containerfile",
+    //                     "Vagrantfile",
+    //                     "Brewfile",
+    //                     "Gemfile",
+    //                     "Pipfile",
+    //                     "build.sbt",
+    //                     "mix.exs",
+    //                     "bsconfig.json",
+    //                     "tsconfig.json",
+    //                 ],
+    //             )
+    //     }
+
+    fn is_image(&self, file: &Path) -> bool {
+        extension_is_one_of(
+            file,
+            &[
+                "png", "jfi", "jfif", "jif", "jpe", "jpeg", "jpg", "gif", "bmp", "tiff", "tif",
+                "ppm", "pgm", "pbm", "pnm", "webp", "raw", "arw", "svg", "stl", "eps", "dvi", "ps",
+                "cbr", "jpf", "cbz", "xpm", "ico", "cr2", "orf", "nef", "heif", "avif", "jxl",
+            ],
+        )
+    }
+
+    fn is_video(&self, file: &Path) -> bool {
+        extension_is_one_of(
+            file,
+            &[
+                "avi", "flv", "m2v", "m4v", "mkv", "mov", "mp4", "mpeg", "mpg", "ogm", "ogv",
+                "vob", "wmv", "webm", "m2ts", "heic",
+            ],
+        )
+    }
+
+    fn is_music(&self, file: &Path) -> bool {
+        extension_is_one_of(file, &["aac", "m4a", "mp3", "ogg", "wma", "mka", "opus"])
+    }
+
+    // Lossless music, rather than any other kind of data...
+    fn is_lossless(&self, file: &Path) -> bool {
+        extension_is_one_of(file, &["alac", "ape", "flac", "wav"])
+    }
+
+    //     #[allow(dead_code)]
+    //     fn is_crypto(&self, file: &Path) -> bool {
+    //         extension_is_one_of(
+    //             file,
+    //             &["asc", "enc", "gpg", "pgp", "sig", "signature", "pfx", "p12"],
+    //         )
+    //     }
+
+    //     #[allow(dead_code)]
+    //     fn is_document(&self, file: &Path) -> bool {
+    //         extension_is_one_of(
+    //             file,
+    //             &[
+    //                 "djvu", "doc", "docx", "dvi", "eml", "eps", "fotd", "key", "keynote", "numbers",
+    //                 "odp", "odt", "pages", "pdf", "ppt", "pptx", "rtf", "xls", "xlsx",
+    //             ],
+    //         )
+    //     }
+
+    //     #[allow(dead_code)]
+    //     fn is_compressed(&self, file: &Path) -> bool {
+    //         extension_is_one_of(
+    //             file,
+    //             &[
+    //                 "zip", "tar", "Z", "z", "gz", "bz2", "a", "ar", "7z", "iso", "dmg", "tc", "rar",
+    //                 "par", "tgz", "xz", "txz", "lz", "tlz", "lzma", "deb", "rpm", "zst", "lz4",
+    //             ],
+    //         )
+    //     }
+
+    //     #[allow(dead_code)]
+    //     fn is_temp(&self, file: &Path) -> bool {
+    //         file.file_name().unwrap().to_str().unwrap().ends_with('~')
+    //             || (file.file_name().unwrap().to_str().unwrap().starts_with('#')
+    //                 && file.file_name().unwrap().to_str().unwrap().ends_with('#'))
+    //             || extension_is_one_of(file, &["tmp", "swp", "swo", "swn", "bak", "bkp", "bk"])
+    //     }
+
+    //     #[allow(dead_code)]
+    //     fn is_compiled(&self, file: &Path) -> bool {
+    //         if extension_is_one_of(file, &["class", "elc", "hi", "o", "pyc", "zwc", "ko"]) {
+    //             true
+    //         // } else if let Some(dir) = file.parent() {
+    //         //     file.get_source_files()
+    //         //         .iter()
+    //         // .any(|path| dir.contains(path))
+    //         } else {
+    //             false
+    //         }
+    //     }
+    // }
+
+    // impl FileColours for FileExtensions {
+    //     fn colour_file(&self, file: &Path) -> Option<Style> {
+    //         use ansi_term::Colour::*;
+
+    //         Some(match file {
+    //             f if self.is_temp(f)        => Fixed(244).normal(),
+    //             f if self.is_immediate(f)   => Yellow.bold().underline(),
+    //             f if self.is_image(f)       => Fixed(133).normal(),
+    //             f if self.is_video(f)       => Fixed(135).normal(),
+    //             f if self.is_music(f)       => Fixed(92).normal(),
+    //             f if self.is_lossless(f)    => Fixed(93).normal(),
+    //             f if self.is_crypto(f)      => Fixed(109).normal(),
+    //             f if self.is_document(f)    => Fixed(105).normal(),
+    //             f if self.is_compressed(f)  => Red.normal(),
+    //             f if self.is_compiled(f)    => Fixed(137).normal(),
+    //             _                           => return None,
+    //         })
+    //     }
+}
+
+impl FileIcon for FileExtensions {
+    fn icon_file(&self, file: &Path) -> Option<char> {
+        if self.is_music(file) || self.is_lossless(file) {
+            Some(Icons::Audio.value())
+        } else if self.is_image(file) {
+            Some(Icons::Image.value())
+        } else if self.is_video(file) {
+            Some(Icons::Video.value())
+        } else {
+            None
+        }
+    }
+}
diff --git a/crates/nu-command/src/viewers/mod.rs b/crates/nu-command/src/viewers/mod.rs
index 6715fb4..29eca7c 100644
--- a/crates/nu-command/src/viewers/mod.rs
+++ b/crates/nu-command/src/viewers/mod.rs
@@ -1,5 +1,6 @@
 mod color_config;
 mod griddle;
+mod icons;
 mod table;
 
 pub use griddle::Griddle;
diff --git a/crates/nu-command/src/viewers/table.rs b/crates/nu-command/src/viewers/table.rs
index 16a08a2..74e0317 100644
--- a/crates/nu-command/src/viewers/table.rs
+++ b/crates/nu-command/src/viewers/table.rs
@@ -107,7 +107,6 @@ impl Command for Table {
                 .into_pipeline_data())
             }
             PipelineData::Value(Value::Error { error }) => Err(error),
-            #[cfg(feature = "custom")]
             PipelineData::Value(Value::CustomValue { val, span }) => {
                 let base_pipeline = val.to_base_value(span)?.into_pipeline_data();
                 self.run(engine_state, stack, call, base_pipeline)
diff --git a/crates/nu-dataframe/Cargo.toml b/crates/nu-dataframe/Cargo.toml
deleted file mode 100644
index 86b86f3..0000000
--- a/crates/nu-dataframe/Cargo.toml
+++ /dev/null
@@ -1,14 +0,0 @@
-[package]
-name = "nu-dataframe"
-version = "0.1.0"
-edition = "2018"
-
-[dependencies]
-chrono = { version="0.4.19", features=["serde"] }
-serde = {version = "1.0.130", features = ["derive"]}
-num = "0.4.0"
-nu-protocol = { path = "../nu-protocol", features = ["custom"] }
-nu-json = { path = "../nu-json"}
-indexmap = { version="1.7.0", features=["serde-1"] }
-polars = { version = "0.17.0", features = ["default", "serde", "object", "checked_arithmetic", "strings"] }
-
diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs
index 52f20c4..e5fa30b 100644
--- a/crates/nu-engine/src/eval.rs
+++ b/crates/nu-engine/src/eval.rs
@@ -24,7 +24,8 @@ fn eval_call(
     call: &Call,
     input: PipelineData,
 ) -> Result<PipelineData, ShellError> {
-    let decl = engine_state.get_decl(call.decl_id);
+    let decl = engine_state.get_decl_with_input(call.decl_id, &input);
+
     if call.named.iter().any(|(flag, _)| flag.item == "help") {
         let full_help = get_full_help(&decl.signature(), &decl.examples(), engine_state);
         Ok(Value::String {
diff --git a/crates/nu-protocol/Cargo.toml b/crates/nu-protocol/Cargo.toml
index e5aa910..179518b 100644
--- a/crates/nu-protocol/Cargo.toml
+++ b/crates/nu-protocol/Cargo.toml
@@ -20,7 +20,6 @@ typetag = "0.1.8"
 
 [features]
 plugin = ["serde_json"]
-custom = []
 
 [dev-dependencies]
 serde_json = "1.0"
diff --git a/crates/nu-protocol/src/ast/call.rs b/crates/nu-protocol/src/ast/call.rs
index 815e18a..f2f9a9c 100644
--- a/crates/nu-protocol/src/ast/call.rs
+++ b/crates/nu-protocol/src/ast/call.rs
@@ -46,6 +46,16 @@ impl Call {
         None
     }
 
+    pub fn get_named_arg(&self, flag_name: &str) -> Option<Spanned<String>> {
+        for name in &self.named {
+            if flag_name == name.0.item {
+                return Some(name.0.clone());
+            }
+        }
+
+        None
+    }
+
     pub fn nth(&self, pos: usize) -> Option<Expression> {
         self.positional.get(pos).cloned()
     }
diff --git a/crates/nu-protocol/src/ast/expression.rs b/crates/nu-protocol/src/ast/expression.rs
index b3cf074..374a970 100644
--- a/crates/nu-protocol/src/ast/expression.rs
+++ b/crates/nu-protocol/src/ast/expression.rs
@@ -53,6 +53,13 @@ impl Expression {
         }
     }
 
+    pub fn as_row_condition_block(&self) -> Option<BlockId> {
+        match self.expr {
+            Expr::RowCondition(block_id) => Some(block_id),
+            _ => None,
+        }
+    }
+
     pub fn as_signature(&self) -> Option<Box<Signature>> {
         match &self.expr {
             Expr::Signature(sig) => Some(sig.clone()),
diff --git a/crates/nu-protocol/src/config.rs b/crates/nu-protocol/src/config.rs
index 1df1871..1dc9045 100644
--- a/crates/nu-protocol/src/config.rs
+++ b/crates/nu-protocol/src/config.rs
@@ -8,6 +8,7 @@ pub struct Config {
     pub table_mode: String,
     pub use_ls_colors: bool,
     pub color_config: HashMap<String, String>,
+    pub use_grid_icons: bool,
 }
 
 impl Default for Config {
@@ -17,6 +18,7 @@ impl Default for Config {
             table_mode: "rounded".into(),
             use_ls_colors: true,
             color_config: HashMap::new(),
+            use_grid_icons: false,
         }
     }
 }
@@ -46,6 +48,9 @@ impl Value {
                     }
                     config.color_config = hm;
                 }
+                "use_grid_icons" => {
+                    config.use_grid_icons = value.as_bool()?;
+                }
                 _ => {}
             }
         }
diff --git a/crates/nu-protocol/src/engine/engine_state.rs b/crates/nu-protocol/src/engine/engine_state.rs
index 176033b..7474e18 100644
--- a/crates/nu-protocol/src/engine/engine_state.rs
+++ b/crates/nu-protocol/src/engine/engine_state.rs
@@ -1,7 +1,7 @@
 use super::Command;
 use crate::{
-    ast::Block, BlockId, DeclId, Example, Overlay, OverlayId, ShellError, Signature, Span, Type,
-    VarId,
+    ast::Block, BlockId, DeclId, Example, Overlay, OverlayId, PipelineData, ShellError, Signature,
+    Span, Type, Value, VarId,
 };
 use core::panic;
 use std::{
@@ -357,6 +357,39 @@ impl EngineState {
             .expect("internal error: missing declaration")
     }
 
+    #[allow(clippy::borrowed_box)]
+    pub fn get_decl_with_input(&self, decl_id: DeclId, input: &PipelineData) -> &Box<dyn Command> {
+        let decl = self.get_decl(decl_id);
+
+        match input {
+            PipelineData::Stream(_) => decl,
+            PipelineData::Value(value) => match value {
+                Value::CustomValue { val, .. } => {
+                    // This filter works because the custom definitions were declared
+                    // before the default nushell declarations. This means that the custom
+                    // declarations that get overridden by the default declarations can only
+                    // be accessed if the input value has the required category
+                    let decls = self
+                        .decls
+                        .iter()
+                        .enumerate()
+                        .filter(|(_, decl_inner)| {
+                            decl.name() == decl_inner.name()
+                                && decl_inner.signature().category == val.category()
+                        })
+                        .map(|(index, _)| index)
+                        .collect::<Vec<usize>>();
+
+                    match decls.first() {
+                        Some(index) => self.get_decl(*index),
+                        None => decl,
+                    }
+                }
+                _ => decl,
+            },
+        }
+    }
+
     pub fn get_signatures(&self) -> Vec<Signature> {
         let mut output = vec![];
         for decl in self.decls.iter() {
@@ -384,6 +417,7 @@ impl EngineState {
             }
         }
 
+        output.sort_by(|a, b| a.0.name.cmp(&b.0.name));
         output
     }
 
diff --git a/crates/nu-protocol/src/lib.rs b/crates/nu-protocol/src/lib.rs
index f8d81b6..2e667f1 100644
--- a/crates/nu-protocol/src/lib.rs
+++ b/crates/nu-protocol/src/lib.rs
@@ -26,7 +26,5 @@ pub use signature::*;
 pub use span::*;
 pub use syntax_shape::*;
 pub use ty::*;
-pub use value::*;
-
-#[cfg(feature = "custom")]
 pub use value::CustomValue;
+pub use value::*;
diff --git a/crates/nu-protocol/src/pipeline_data.rs b/crates/nu-protocol/src/pipeline_data.rs
index 2735682..c47dfae 100644
--- a/crates/nu-protocol/src/pipeline_data.rs
+++ b/crates/nu-protocol/src/pipeline_data.rs
@@ -31,6 +31,7 @@ use crate::{ast::PathMember, Config, ShellError, Span, Value, ValueStream};
 /// * A balance of the two approaches is what we've landed on: Values are thread-safe to pass, and we can stream
 /// them into any sources. Streams are still available to model the infinite streams approach of original
 /// Nushell.
+#[derive(Debug)]
 pub enum PipelineData {
     Value(Value),
     Stream(ValueStream),
diff --git a/crates/nu-protocol/src/shell_error.rs b/crates/nu-protocol/src/shell_error.rs
index cdc1a84..56def77 100644
--- a/crates/nu-protocol/src/shell_error.rs
+++ b/crates/nu-protocol/src/shell_error.rs
@@ -76,6 +76,10 @@ pub enum ShellError {
     #[diagnostic(code(nu::shell::external_commands), url(docsrs))]
     ExternalNotSupported(#[label = "external not supported"] Span),
 
+    #[error("Invalid Probability.")]
+    #[diagnostic(code(nu::shell::invalid_probability), url(docsrs))]
+    InvalidProbability(#[label = "invalid probability"] Span),
+
     #[error("Internal error: {0}.")]
     #[diagnostic(code(nu::shell::internal_error), url(docsrs))]
     InternalError(String),
@@ -210,6 +214,10 @@ pub enum ShellError {
     #[error("Casting error")]
     #[diagnostic(code(nu::parser::downcast_not_possible), url(docsrs))]
     DowncastNotPossible(String, #[label("{0}")] Span),
+
+    #[error("{0}")]
+    #[diagnostic()]
+    LabeledError(String, String, #[label("{1}")] Span),
 }
 
 impl From<std::io::Error> for ShellError {
diff --git a/crates/nu-protocol/src/signature.rs b/crates/nu-protocol/src/signature.rs
index 6908d92..0b6b740 100644
--- a/crates/nu-protocol/src/signature.rs
+++ b/crates/nu-protocol/src/signature.rs
@@ -42,6 +42,7 @@ pub enum Category {
     Filters,
     Formats,
     Math,
+    Random,
     Platform,
     Shells,
     Strings,
@@ -63,6 +64,7 @@ impl std::fmt::Display for Category {
             Category::Filters => "filters",
             Category::Formats => "formats",
             Category::Math => "math",
+            Category::Random => "random",
             Category::Platform => "platform",
             Category::Shells => "shells",
             Category::Strings => "strings",
diff --git a/crates/nu-protocol/src/value/custom_value.rs b/crates/nu-protocol/src/value/custom_value.rs
index c11d352..92423d5 100644
--- a/crates/nu-protocol/src/value/custom_value.rs
+++ b/crates/nu-protocol/src/value/custom_value.rs
@@ -1,12 +1,14 @@
-use std::fmt;
+use std::{cmp::Ordering, fmt};
 
-use crate::{ast::Operator, ShellError, Span, Value};
+use crate::{ast::Operator, Category, 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;
 
@@ -27,6 +29,9 @@ pub trait CustomValue: fmt::Debug + Send + Sync {
     fn follow_path_int(&self, count: usize, span: Span) -> Result<Value, ShellError>;
     fn follow_path_string(&self, column_name: String, span: Span) -> Result<Value, ShellError>;
 
+    // ordering with other value
+    fn partial_cmp(&self, other: &Value) -> Option<Ordering>;
+
     // Definition of an operation between the object that implements the trait
     // and another Value.
     // The Operator enum is used to indicate the expected operation
diff --git a/crates/nu-protocol/src/value/from.rs b/crates/nu-protocol/src/value/from.rs
index f61253b..5185bb9 100644
--- a/crates/nu-protocol/src/value/from.rs
+++ b/crates/nu-protocol/src/value/from.rs
@@ -1,5 +1,14 @@
 use crate::{ShellError, Span, Value};
 
+impl From<String> for Value {
+    fn from(val: String) -> Self {
+        Value::String {
+            val,
+            span: Span::unknown(),
+        }
+    }
+}
+
 impl From<bool> for Value {
     fn from(val: bool) -> Self {
         Value::Bool {
diff --git a/crates/nu-protocol/src/value/mod.rs b/crates/nu-protocol/src/value/mod.rs
index d4c5315..8e041bc 100644
--- a/crates/nu-protocol/src/value/mod.rs
+++ b/crates/nu-protocol/src/value/mod.rs
@@ -18,10 +18,7 @@ use std::{cmp::Ordering, fmt::Debug};
 use crate::ast::{CellPath, PathMember};
 use crate::{did_you_mean, span, BlockId, Config, Span, Spanned, Type};
 
-#[cfg(feature = "custom")]
 use crate::ast::Operator;
-
-#[cfg(feature = "custom")]
 pub use custom_value::CustomValue;
 
 use crate::ShellError;
@@ -88,7 +85,6 @@ pub enum Value {
         val: CellPath,
         span: Span,
     },
-    #[cfg(feature = "custom")]
     CustomValue {
         val: Box<dyn CustomValue>,
         span: Span,
@@ -155,7 +151,6 @@ impl Clone for Value {
                 val: val.clone(),
                 span: *span,
             },
-            #[cfg(feature = "custom")]
             Value::CustomValue { val, span } => val.clone_value(*span),
         }
     }
@@ -224,7 +219,6 @@ impl Value {
             Value::Nothing { span, .. } => Ok(*span),
             Value::Binary { span, .. } => Ok(*span),
             Value::CellPath { span, .. } => Ok(*span),
-            #[cfg(feature = "custom")]
             Value::CustomValue { span, .. } => Ok(*span),
         }
     }
@@ -247,7 +241,6 @@ impl Value {
             Value::Error { .. } => {}
             Value::Binary { span, .. } => *span = new_span,
             Value::CellPath { span, .. } => *span = new_span,
-            #[cfg(feature = "custom")]
             Value::CustomValue { span, .. } => *span = new_span,
         }
 
@@ -277,7 +270,6 @@ impl Value {
             Value::Error { .. } => Type::Error,
             Value::Binary { .. } => Type::Binary,
             Value::CellPath { .. } => Type::CellPath,
-            #[cfg(feature = "custom")]
             Value::CustomValue { .. } => Type::Custom,
         }
     }
@@ -319,7 +311,6 @@ impl Value {
             Value::Error { error } => format!("{:?}", error),
             Value::Binary { val, .. } => format!("{:?}", val),
             Value::CellPath { val, .. } => val.into_string(),
-            #[cfg(feature = "custom")]
             Value::CustomValue { val, .. } => val.value_string(),
         }
     }
@@ -361,7 +352,6 @@ impl Value {
             Value::Error { error } => format!("{:?}", error),
             Value::Binary { val, .. } => format!("{:?}", val),
             Value::CellPath { val, .. } => val.into_string(),
-            #[cfg(feature = "custom")]
             Value::CustomValue { val, .. } => val.value_string(),
         }
     }
@@ -408,7 +398,6 @@ impl Value {
                                 return Err(ShellError::AccessBeyondEndOfStream(*origin_span));
                             }
                         }
-                        #[cfg(feature = "custom")]
                         Value::CustomValue { val, .. } => {
                             current = val.follow_path_int(*count, *origin_span)?;
                         }
@@ -459,7 +448,6 @@ impl Value {
                             span: *span,
                         };
                     }
-                    #[cfg(feature = "custom")]
                     Value::CustomValue { val, .. } => {
                         current = val.follow_path_string(column_name.clone(), *origin_span)?;
                     }
@@ -652,6 +640,7 @@ impl PartialOrd for Value {
             (Value::Binary { val: lhs, .. }, Value::Binary { val: rhs, .. }) => {
                 lhs.partial_cmp(rhs)
             }
+            (Value::CustomValue { val: lhs, .. }, rhs) => lhs.partial_cmp(rhs),
             (Value::Nothing { .. }, Value::Nothing { .. }) => Some(Ordering::Equal),
             (_, _) => None,
         }
@@ -725,7 +714,6 @@ impl Value {
                 }
             }
 
-            #[cfg(feature = "custom")]
             (Value::CustomValue { val: lhs, span }, rhs) => {
                 lhs.operation(*span, Operator::Plus, op, rhs)
             }
@@ -795,7 +783,6 @@ impl Value {
                 }
             }
 
-            #[cfg(feature = "custom")]
             (Value::CustomValue { val: lhs, span }, rhs) => {
                 lhs.operation(*span, Operator::Minus, op, rhs)
             }
@@ -835,7 +822,6 @@ impl Value {
                 val: lhs * rhs,
                 span,
             }),
-            #[cfg(feature = "custom")]
             (Value::CustomValue { val: lhs, span }, rhs) => {
                 lhs.operation(*span, Operator::Multiply, op, rhs)
             }
@@ -900,7 +886,6 @@ impl Value {
                     Err(ShellError::DivisionByZero(op))
                 }
             }
-            #[cfg(feature = "custom")]
             (Value::CustomValue { val: lhs, span }, rhs) => {
                 lhs.operation(*span, Operator::Divide, op, rhs)
             }
@@ -917,7 +902,6 @@ impl Value {
     pub fn lt(&self, op: Span, rhs: &Value) -> Result<Value, ShellError> {
         let span = span(&[self.span()?, rhs.span()?]);
 
-        #[cfg(feature = "custom")]
         if let (Value::CustomValue { val: lhs, span }, rhs) = (self, rhs) {
             return lhs.operation(*span, Operator::LessThan, op, rhs);
         }
@@ -939,7 +923,6 @@ impl Value {
     pub fn lte(&self, op: Span, rhs: &Value) -> Result<Value, ShellError> {
         let span = span(&[self.span()?, rhs.span()?]);
 
-        #[cfg(feature = "custom")]
         if let (Value::CustomValue { val: lhs, span }, rhs) = (self, rhs) {
             return lhs.operation(*span, Operator::LessThanOrEqual, op, rhs);
         }
@@ -961,7 +944,6 @@ impl Value {
     pub fn gt(&self, op: Span, rhs: &Value) -> Result<Value, ShellError> {
         let span = span(&[self.span()?, rhs.span()?]);
 
-        #[cfg(feature = "custom")]
         if let (Value::CustomValue { val: lhs, span }, rhs) = (self, rhs) {
             return lhs.operation(*span, Operator::GreaterThan, op, rhs);
         }
@@ -983,7 +965,6 @@ impl Value {
     pub fn gte(&self, op: Span, rhs: &Value) -> Result<Value, ShellError> {
         let span = span(&[self.span()?, rhs.span()?]);
 
-        #[cfg(feature = "custom")]
         if let (Value::CustomValue { val: lhs, span }, rhs) = (self, rhs) {
             return lhs.operation(*span, Operator::GreaterThanOrEqual, op, rhs);
         }
@@ -1005,7 +986,6 @@ impl Value {
     pub fn eq(&self, op: Span, rhs: &Value) -> Result<Value, ShellError> {
         let span = span(&[self.span()?, rhs.span()?]);
 
-        #[cfg(feature = "custom")]
         if let (Value::CustomValue { val: lhs, span }, rhs) = (self, rhs) {
             return lhs.operation(*span, Operator::Equal, op, rhs);
         }
@@ -1027,7 +1007,6 @@ impl Value {
     pub fn ne(&self, op: Span, rhs: &Value) -> Result<Value, ShellError> {
         let span = span(&[self.span()?, rhs.span()?]);
 
-        #[cfg(feature = "custom")]
         if let (Value::CustomValue { val: lhs, span }, rhs) = (self, rhs) {
             return lhs.operation(*span, Operator::NotEqual, op, rhs);
         }
@@ -1067,7 +1046,6 @@ impl Value {
                 val: rhs.contains(lhs),
                 span,
             }),
-            #[cfg(feature = "custom")]
             (Value::CustomValue { val: lhs, span }, rhs) => {
                 lhs.operation(*span, Operator::In, op, rhs)
             }
@@ -1101,7 +1079,6 @@ impl Value {
                 val: !rhs.contains(lhs),
                 span,
             }),
-            #[cfg(feature = "custom")]
             (Value::CustomValue { val: lhs, span }, rhs) => {
                 lhs.operation(*span, Operator::NotIn, op, rhs)
             }
@@ -1123,7 +1100,6 @@ impl Value {
                 val: lhs.contains(rhs),
                 span,
             }),
-            #[cfg(feature = "custom")]
             (Value::CustomValue { val: lhs, span }, rhs) => {
                 lhs.operation(*span, Operator::Contains, op, rhs)
             }
@@ -1145,7 +1121,6 @@ impl Value {
                 val: !lhs.contains(rhs),
                 span,
             }),
-            #[cfg(feature = "custom")]
             (Value::CustomValue { val: lhs, span }, rhs) => {
                 lhs.operation(*span, Operator::NotContains, op, rhs)
             }
@@ -1203,7 +1178,6 @@ impl Value {
                     Err(ShellError::DivisionByZero(op))
                 }
             }
-            #[cfg(feature = "custom")]
             (Value::CustomValue { val: lhs, span }, rhs) => {
                 lhs.operation(*span, Operator::Modulo, op, rhs)
             }
@@ -1226,7 +1200,6 @@ impl Value {
                 val: *lhs && *rhs,
                 span,
             }),
-            #[cfg(feature = "custom")]
             (Value::CustomValue { val: lhs, span }, rhs) => {
                 lhs.operation(*span, Operator::And, op, rhs)
             }
@@ -1248,7 +1221,6 @@ impl Value {
                 val: *lhs || *rhs,
                 span,
             }),
-            #[cfg(feature = "custom")]
             (Value::CustomValue { val: lhs, span }, rhs) => {
                 lhs.operation(*span, Operator::Or, op, rhs)
             }
@@ -1288,7 +1260,6 @@ impl Value {
                 val: lhs.powf(*rhs),
                 span,
             }),
-            #[cfg(feature = "custom")]
             (Value::CustomValue { val: lhs, span }, rhs) => {
                 lhs.operation(*span, Operator::Pow, op, rhs)
             }
diff --git a/src/main.rs b/src/main.rs
index 0cfe93c..9bda4f8 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -230,6 +230,18 @@ fn main() -> Result<()> {
             //Reset the ctrl-c handler
             ctrlc.store(false, Ordering::SeqCst);
 
+            // Get animation milliseconds every loop, if not set, the animation
+            // is defaulted to 1 second. If set to some non-number value,
+            // like "OFF", then the animation is disabled. If set to some number
+            // like "5000" then the animation will run at 5 seconds.
+            let animate_ms = match std::env::var("EQ_PROMPT_ANIMATE_MS") {
+                Ok(ms_str) => match ms_str.parse::<u64>() {
+                    Ok(ms_int) => Some(ms_int),
+                    _ => None,
+                },
+                _ => Some(1000),
+            };
+
             let line_editor = Reedline::create()
                 .into_diagnostic()?
                 .with_completion_action_handler(Box::new(FuzzyCompletion {
@@ -238,6 +250,7 @@ fn main() -> Result<()> {
                 .with_highlighter(Box::new(NuHighlighter {
                     engine_state: engine_state.clone(),
                 }))
+                .with_repaint(animate_ms)
                 // .with_completion_action_handler(Box::new(
                 //     ListCompletionHandler::default().with_completer(Box::new(completer)),
                 // ))


Test patch:
diff --git a/crates/nu-command/src/dataframe/nu_dataframe/commands/test_dataframe.rs b/crates/nu-command/src/dataframe/nu_dataframe/commands/test_dataframe.rs
new file mode 100644
index 0000000..653f393
--- /dev/null
+++ b/crates/nu-command/src/dataframe/nu_dataframe/commands/test_dataframe.rs
@@ -0,0 +1,87 @@
+use nu_engine::eval_block;
+use nu_parser::parse;
+use nu_protocol::{
+    engine::{Command, EngineState, Stack, StateWorkingSet},
+    PipelineData, Span, Value, CONFIG_VARIABLE_ID,
+};
+
+use super::ToDataFrame;
+
+pub fn test_dataframe(cmd: impl Command + 'static) {
+    let examples = cmd.examples();
+    let mut engine_state = Box::new(EngineState::new());
+
+    let delta = {
+        // Base functions that are needed for testing
+        // Try to keep this working set small to keep tests running as fast as possible
+        let mut working_set = StateWorkingSet::new(&*engine_state);
+        working_set.add_decl(Box::new(ToDataFrame));
+
+        // Adding the command that is being tested to the working set
+        working_set.add_decl(Box::new(cmd));
+
+        working_set.render()
+    };
+
+    let _ = engine_state.merge_delta(delta);
+
+    for example in examples {
+        // Skip tests that don't have results to compare to
+        if example.result.is_none() {
+            continue;
+        }
+        let start = std::time::Instant::now();
+
+        let (block, delta) = {
+            let mut working_set = StateWorkingSet::new(&*engine_state);
+            let (output, err) = parse(&mut working_set, None, example.example.as_bytes(), false);
+
+            if let Some(err) = err {
+                panic!("test parse error in `{}`: {:?}", example.example, err)
+            }
+
+            (output, working_set.render())
+        };
+
+        let _ = engine_state.merge_delta(delta);
+
+        let mut stack = Stack::new();
+
+        // Set up our initial config to start from
+        stack.vars.insert(
+            CONFIG_VARIABLE_ID,
+            Value::Record {
+                cols: vec![],
+                vals: vec![],
+                span: Span::unknown(),
+            },
+        );
+
+        match eval_block(
+            &engine_state,
+            &mut stack,
+            &block,
+            PipelineData::new(Span::unknown()),
+        ) {
+            Err(err) => panic!("test eval error in `{}`: {:?}", example.example, err),
+            Ok(result) => {
+                let result = result.into_value(Span::unknown());
+                println!("input: {}", example.example);
+                println!("result: {:?}", result);
+                println!("done: {:?}", start.elapsed());
+
+                // Note. Value implements PartialEq for Bool, Int, Float, String and Block
+                // If the command you are testing requires to compare another case, then
+                // you need to define its equality in the Value struct
+                if let Some(expected) = example.result {
+                    if result != expected {
+                        panic!(
+                            "the example result is different to expected value: {:?} != {:?}",
+                            result, expected
+                        )
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/crates/nu-command/src/example_test.rs b/crates/nu-command/src/example_test.rs
index 1c7d006..da5789c 100644
--- a/crates/nu-command/src/example_test.rs
+++ b/crates/nu-command/src/example_test.rs
@@ -7,7 +7,7 @@ use nu_protocol::{
 
 use crate::To;
 
-use super::{Date, From, Into, Math, Split, Str};
+use super::{Date, From, Into, Math, Random, Split, Str};
 
 pub fn test_examples(cmd: impl Command + 'static) {
     let examples = cmd.examples();
@@ -21,6 +21,7 @@ pub fn test_examples(cmd: impl Command + 'static) {
         working_set.add_decl(Box::new(From));
         working_set.add_decl(Box::new(To));
         working_set.add_decl(Box::new(Into));
+        working_set.add_decl(Box::new(Random));
         working_set.add_decl(Box::new(Split));
         working_set.add_decl(Box::new(Math));
         working_set.add_decl(Box::new(Date));