Repository: nushell/engine-q
Pull request #457 (2021-12-09 20:33:10)

Dataframe commands name
Change names in dataframe commands to use the word `dataframe`


Patch:
diff --git a/Cargo.lock b/Cargo.lock
index bc7d11b..2dff56a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1610,6 +1610,7 @@ dependencies = [
  "toml",
  "trash",
  "unicode-segmentation",
+ "url",
  "uuid",
 ]
 
@@ -2368,12 +2369,13 @@ dependencies = [
 [[package]]
 name = "reedline"
 version = "0.2.0"
-source = "git+https://github.com/nushell/reedline?branch=main#2bc7046066f3d3f2a9a538fc3d546adf1e5d4b28"
+source = "git+https://github.com/nushell/reedline?branch=main#6884ec4062a1b2184a6d47553101d3575d4e8b61"
 dependencies = [
  "chrono",
  "crossterm",
  "nu-ansi-term 0.39.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde",
+ "strip-ansi-escapes",
  "unicode-segmentation",
  "unicode-width",
 ]
diff --git a/crates/nu-command/Cargo.toml b/crates/nu-command/Cargo.toml
index 6c6c48e..68a76f0 100644
--- a/crates/nu-command/Cargo.toml
+++ b/crates/nu-command/Cargo.toml
@@ -16,6 +16,7 @@ nu-parser = { path = "../nu-parser" }
 nu-ansi-term = { path = "../nu-ansi-term" }
 
 # Potential dependencies for extras
+url = "2.2.1"
 csv = "1.1.3"
 glob = "0.3.0"
 Inflector = "0.11"
diff --git a/crates/nu-command/src/dataframe/append.rs b/crates/nu-command/src/dataframe/append.rs
index 52ad5f3..914ff71 100644
--- a/crates/nu-command/src/dataframe/append.rs
+++ b/crates/nu-command/src/dataframe/append.rs
@@ -12,7 +12,7 @@ pub struct AppendDF;
 
 impl Command for AppendDF {
     fn name(&self) -> &str {
-        "append-df"
+        "dataframe append"
     }
 
     fn usage(&self) -> &str {
@@ -30,8 +30,8 @@ impl Command for AppendDF {
         vec![
             Example {
                 description: "Appends a dataframe as new columns",
-                example: r#"let a = ([[a b]; [1 2] [3 4]] | to df);
-$a | append-df $a"#,
+                example: r#"let a = ([[a b]; [1 2] [3 4]] | dataframe to-df);
+$a | dataframe append $a"#,
                 result: Some(
                     NuDataFrame::try_from_columns(vec![
                         Column::new("a".to_string(), vec![1.into(), 3.into()]),
@@ -46,8 +46,8 @@ $a | append-df $a"#,
             Example {
                 description: "Appends a dataframe merging at the end of columns",
                 //example: r#"let a = ([[a b]; [1 2] [3 4]] | to df); $a | append-df $a -col"#,
-                example: r#"let a = ([[a b]; [1 2] [3 4]] | to df);
-$a | append-df $a --col"#,
+                example: r#"let a = ([[a b]; [1 2] [3 4]] | dataframe to-df);
+$a | dataframe append $a --col"#,
                 result: Some(
                     NuDataFrame::try_from_columns(vec![
                         Column::new(
diff --git a/crates/nu-command/src/dataframe/column.rs b/crates/nu-command/src/dataframe/column.rs
index 422dd71..d0d84c1 100644
--- a/crates/nu-command/src/dataframe/column.rs
+++ b/crates/nu-command/src/dataframe/column.rs
@@ -12,7 +12,7 @@ pub struct ColumnDF;
 
 impl Command for ColumnDF {
     fn name(&self) -> &str {
-        "column"
+        "dataframe column"
     }
 
     fn usage(&self) -> &str {
@@ -28,7 +28,7 @@ impl Command for ColumnDF {
     fn examples(&self) -> Vec<Example> {
         vec![Example {
             description: "Returns the selected column as series",
-            example: "[[a b]; [1 2] [3 4]] | to df | column a",
+            example: "[[a b]; [1 2] [3 4]] | dataframe to-df | dataframe column a",
             result: Some(
                 NuDataFrame::try_from_columns(vec![Column::new(
                     "a".to_string(),
diff --git a/crates/nu-command/src/dataframe/command.rs b/crates/nu-command/src/dataframe/command.rs
new file mode 100644
index 0000000..5ea43a2
--- /dev/null
+++ b/crates/nu-command/src/dataframe/command.rs
@@ -0,0 +1,37 @@
+use nu_engine::get_full_help;
+use nu_protocol::{
+    ast::Call,
+    engine::{Command, EngineState, Stack},
+    Category, IntoPipelineData, PipelineData, ShellError, Signature, Value,
+};
+
+#[derive(Clone)]
+pub struct Dataframe;
+
+impl Command for Dataframe {
+    fn name(&self) -> &str {
+        "dataframe"
+    }
+
+    fn usage(&self) -> &str {
+        "Dataframe commands"
+    }
+
+    fn signature(&self) -> Signature {
+        Signature::build(self.name()).category(Category::Custom("dataframe".into()))
+    }
+
+    fn run(
+        &self,
+        engine_state: &EngineState,
+        _stack: &mut Stack,
+        call: &Call,
+        _input: PipelineData,
+    ) -> Result<PipelineData, ShellError> {
+        Ok(Value::String {
+            val: get_full_help(&Dataframe.signature(), &Dataframe.examples(), engine_state),
+            span: call.head,
+        }
+        .into_pipeline_data())
+    }
+}
diff --git a/crates/nu-command/src/dataframe/describe.rs b/crates/nu-command/src/dataframe/describe.rs
index 80bf415..93a4458 100644
--- a/crates/nu-command/src/dataframe/describe.rs
+++ b/crates/nu-command/src/dataframe/describe.rs
@@ -17,7 +17,7 @@ pub struct DescribeDF;
 
 impl Command for DescribeDF {
     fn name(&self) -> &str {
-        "describe-df"
+        "dataframe describe"
     }
 
     fn usage(&self) -> &str {
@@ -31,7 +31,7 @@ impl Command for DescribeDF {
     fn examples(&self) -> Vec<Example> {
         vec![Example {
             description: "dataframe description",
-            example: "[[a b]; [1 1] [1 1]] | to df | describe-df",
+            example: "[[a b]; [1 1] [1 1]] | dataframe to-df | dataframe describe",
             result: Some(
                 NuDataFrame::try_from_columns(vec![
                     Column::new(
diff --git a/crates/nu-command/src/dataframe/drop.rs b/crates/nu-command/src/dataframe/drop.rs
index 7be2361..c632d0f 100644
--- a/crates/nu-command/src/dataframe/drop.rs
+++ b/crates/nu-command/src/dataframe/drop.rs
@@ -13,7 +13,7 @@ pub struct DropDF;
 
 impl Command for DropDF {
     fn name(&self) -> &str {
-        "drop-df"
+        "dataframe drop"
     }
 
     fn usage(&self) -> &str {
@@ -29,7 +29,7 @@ impl Command for DropDF {
     fn examples(&self) -> Vec<Example> {
         vec![Example {
             description: "drop column a",
-            example: "[[a b]; [1 2] [3 4]] | to df | drop-df a",
+            example: "[[a b]; [1 2] [3 4]] | dataframe to-df | dataframe drop a",
             result: Some(
                 NuDataFrame::try_from_columns(vec![Column::new(
                     "b".to_string(),
diff --git a/crates/nu-command/src/dataframe/dtypes.rs b/crates/nu-command/src/dataframe/dtypes.rs
index 3152f0d..56da61b 100644
--- a/crates/nu-command/src/dataframe/dtypes.rs
+++ b/crates/nu-command/src/dataframe/dtypes.rs
@@ -10,7 +10,7 @@ pub struct DataTypes;
 
 impl Command for DataTypes {
     fn name(&self) -> &str {
-        "dtypes"
+        "dataframe dtypes"
     }
 
     fn usage(&self) -> &str {
@@ -24,7 +24,7 @@ impl Command for DataTypes {
     fn examples(&self) -> Vec<Example> {
         vec![Example {
             description: "Dataframe dtypes",
-            example: "[[a b]; [1 2] [3 4]] | to df | dtypes",
+            example: "[[a b]; [1 2] [3 4]] | dataframe to-df | dataframe dtypes",
             result: Some(
                 NuDataFrame::try_from_columns(vec![
                     Column::new(
diff --git a/crates/nu-command/src/dataframe/mod.rs b/crates/nu-command/src/dataframe/mod.rs
index beab822..20fe6bc 100644
--- a/crates/nu-command/src/dataframe/mod.rs
+++ b/crates/nu-command/src/dataframe/mod.rs
@@ -1,7 +1,9 @@
+mod series;
 mod values;
 
 mod append;
 mod column;
+mod command;
 mod describe;
 mod drop;
 mod dtypes;
@@ -10,6 +12,7 @@ mod to_df;
 
 pub use append::AppendDF;
 pub use column::ColumnDF;
+pub use command::Dataframe;
 pub use describe::DescribeDF;
 pub use drop::DropDF;
 pub use dtypes::DataTypes;
@@ -31,6 +34,7 @@ pub fn add_dataframe_decls(working_set: &mut StateWorkingSet) {
     bind_command!(
         AppendDF,
         ColumnDF,
+        Dataframe,
         DataTypes,
         DescribeDF,
         DropDF,
diff --git a/crates/nu-command/src/dataframe/open.rs b/crates/nu-command/src/dataframe/open.rs
index 19fb85f..7c82f10 100644
--- a/crates/nu-command/src/dataframe/open.rs
+++ b/crates/nu-command/src/dataframe/open.rs
@@ -14,7 +14,7 @@ pub struct OpenDataFrame;
 
 impl Command for OpenDataFrame {
     fn name(&self) -> &str {
-        "open-df"
+        "dataframe open"
     }
 
     fn usage(&self) -> &str {
@@ -63,7 +63,7 @@ impl Command for OpenDataFrame {
     fn examples(&self) -> Vec<Example> {
         vec![Example {
             description: "Takes a file name and creates a dataframe",
-            example: "open-df test.csv",
+            example: "dataframe open test.csv",
             result: None,
         }]
     }
diff --git a/crates/nu-command/src/dataframe/series/mod.rs b/crates/nu-command/src/dataframe/series/mod.rs
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/crates/nu-command/src/dataframe/series/mod.rs
@@ -0,0 +1,1 @@
+
diff --git a/crates/nu-command/src/dataframe/to_df.rs b/crates/nu-command/src/dataframe/to_df.rs
index 603d56f..308d61a 100644
--- a/crates/nu-command/src/dataframe/to_df.rs
+++ b/crates/nu-command/src/dataframe/to_df.rs
@@ -11,7 +11,7 @@ pub struct ToDataFrame;
 
 impl Command for ToDataFrame {
     fn name(&self) -> &str {
-        "to df"
+        "dataframe to-df"
     }
 
     fn usage(&self) -> &str {
@@ -26,7 +26,7 @@ impl Command for ToDataFrame {
         vec![
             Example {
                 description: "Takes a dictionary and creates a dataframe",
-                example: "[[a b];[1 2] [3 4]] | to df",
+                example: "[[a b];[1 2] [3 4]] | dataframe to-df",
                 result: Some(
                     NuDataFrame::try_from_columns(vec![
                         Column::new("a".to_string(), vec![1.into(), 3.into()]),
@@ -38,7 +38,7 @@ impl Command for ToDataFrame {
             },
             Example {
                 description: "Takes a list of tables and creates a dataframe",
-                example: "[[1 2 a] [3 4 b] [5 6 c]] | to df",
+                example: "[[1 2 a] [3 4 b] [5 6 c]] | dataframe to-df",
                 result: Some(
                     NuDataFrame::try_from_columns(vec![
                         Column::new("0".to_string(), vec![1.into(), 3.into(), 5.into()]),
@@ -58,7 +58,7 @@ impl Command for ToDataFrame {
             },
             Example {
                 description: "Takes a list and creates a dataframe",
-                example: "[a b c] | to df",
+                example: "[a b c] | dataframe to-df",
                 result: Some(
                     NuDataFrame::try_from_columns(vec![Column::new(
                         "0".to_string(),
@@ -74,7 +74,7 @@ impl Command for ToDataFrame {
             },
             Example {
                 description: "Takes a list of booleans and creates a dataframe",
-                example: "[$true $true $false] | to df",
+                example: "[$true $true $false] | dataframe to-df",
                 result: Some(
                     NuDataFrame::try_from_columns(vec![Column::new(
                         "0".to_string(),
diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs
index c354625..4099939 100644
--- a/crates/nu-command/src/default_context.rs
+++ b/crates/nu-command/src/default_context.rs
@@ -113,6 +113,7 @@ pub fn create_default_context() -> EngineState {
             Mkdir,
             Module,
             Mv,
+            Nth,
             ParEach,
             Parse,
             Prepend,
diff --git a/crates/nu-command/src/filters/mod.rs b/crates/nu-command/src/filters/mod.rs
index 7f99013..f9a8a7b 100644
--- a/crates/nu-command/src/filters/mod.rs
+++ b/crates/nu-command/src/filters/mod.rs
@@ -9,6 +9,7 @@ mod get;
 mod last;
 mod length;
 mod lines;
+mod nth;
 mod par_each;
 mod prepend;
 mod range;
@@ -34,6 +35,7 @@ pub use get::Get;
 pub use last::Last;
 pub use length::Length;
 pub use lines::Lines;
+pub use nth::Nth;
 pub use par_each::ParEach;
 pub use prepend::Prepend;
 pub use range::Range;
diff --git a/crates/nu-command/src/filters/nth.rs b/crates/nu-command/src/filters/nth.rs
new file mode 100644
index 0000000..6597db9
--- /dev/null
+++ b/crates/nu-command/src/filters/nth.rs
@@ -0,0 +1,152 @@
+use nu_engine::CallExt;
+use nu_protocol::ast::Call;
+use nu_protocol::engine::{Command, EngineState, Stack};
+use nu_protocol::{
+    Category, Example, IntoInterruptiblePipelineData, PipelineData, PipelineIterator, ShellError,
+    Signature, Span, SyntaxShape, Value,
+};
+
+#[derive(Clone)]
+pub struct Nth;
+
+impl Command for Nth {
+    fn name(&self) -> &str {
+        "nth"
+    }
+
+    fn signature(&self) -> Signature {
+        Signature::build("nth")
+            .rest("rest", SyntaxShape::Int, "the number of the row to return")
+            .switch("skip", "Skip the rows instead of selecting them", Some('s'))
+            .category(Category::Filters)
+    }
+
+    fn usage(&self) -> &str {
+        "Return or skip only the selected rows."
+    }
+
+    fn examples(&self) -> Vec<Example> {
+        vec![
+            Example {
+                example: "[sam,sarah,2,3,4,5] | nth 0 1 2",
+                description: "Get the first, second, and third row",
+                result: Some(Value::List {
+                    vals: vec![
+                        Value::test_string("sam"),
+                        Value::test_string("sarah"),
+                        Value::test_int(2),
+                    ],
+                    span: Span::unknown(),
+                }),
+            },
+            Example {
+                example: "[0,1,2,3,4,5] | nth 0 1 2",
+                description: "Get the first, second, and third row",
+                result: Some(Value::List {
+                    vals: vec![Value::test_int(0), Value::test_int(1), Value::test_int(2)],
+                    span: Span::unknown(),
+                }),
+            },
+            Example {
+                example: "[0,1,2,3,4,5] | nth -s 0 1 2",
+                description: "Skip the first, second, and third row",
+                result: Some(Value::List {
+                    vals: vec![Value::test_int(3), Value::test_int(4), Value::test_int(5)],
+                    span: Span::unknown(),
+                }),
+            },
+            Example {
+                example: "[0,1,2,3,4,5] | nth 0 2 4",
+                description: "Get the first, third, and fifth row",
+                result: Some(Value::List {
+                    vals: vec![Value::test_int(0), Value::test_int(2), Value::test_int(4)],
+                    span: Span::unknown(),
+                }),
+            },
+            Example {
+                example: "[0,1,2,3,4,5] | nth 2 0 4",
+                description: "Get the first, third, and fifth row",
+                result: Some(Value::List {
+                    vals: vec![Value::test_int(0), Value::test_int(2), Value::test_int(4)],
+                    span: Span::unknown(),
+                }),
+            },
+        ]
+    }
+
+    fn run(
+        &self,
+        engine_state: &EngineState,
+        stack: &mut Stack,
+        call: &Call,
+        input: PipelineData,
+    ) -> Result<PipelineData, ShellError> {
+        let mut rows: Vec<usize> = call.rest(engine_state, stack, 0)?;
+        rows.sort_unstable();
+        let skip = call.has_flag("skip");
+        let pipeline_iter: PipelineIterator = input.into_iter();
+
+        Ok(NthIterator {
+            input: pipeline_iter,
+            rows,
+            skip,
+            current: 0,
+        }
+        .into_pipeline_data(engine_state.ctrlc.clone()))
+    }
+}
+
+struct NthIterator {
+    input: PipelineIterator,
+    rows: Vec<usize>,
+    skip: bool,
+    current: usize,
+}
+
+impl Iterator for NthIterator {
+    type Item = Value;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        loop {
+            if !self.skip {
+                if let Some(row) = self.rows.get(0) {
+                    if self.current == *row {
+                        self.rows.remove(0);
+                        self.current += 1;
+                        return self.input.next();
+                    } else {
+                        self.current += 1;
+                        let _ = self.input.next();
+                        continue;
+                    }
+                } else {
+                    return None;
+                }
+            } else if let Some(row) = self.rows.get(0) {
+                if self.current == *row {
+                    self.rows.remove(0);
+                    self.current += 1;
+                    let _ = self.input.next();
+                    continue;
+                } else {
+                    self.current += 1;
+                    return self.input.next();
+                }
+            } else {
+                return self.input.next();
+            }
+        }
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    #[test]
+    fn test_examples() {
+        use crate::test_examples;
+
+        test_examples(Nth {})
+    }
+}
diff --git a/crates/nu-command/src/formats/from/mod.rs b/crates/nu-command/src/formats/from/mod.rs
index 54f007d..7f650d1 100644
--- a/crates/nu-command/src/formats/from/mod.rs
+++ b/crates/nu-command/src/formats/from/mod.rs
@@ -17,6 +17,7 @@ mod yaml;
 
 pub use self::csv::FromCsv;
 pub use self::toml::FromToml;
+pub use self::url::FromUrl;
 pub use command::From;
 pub use eml::FromEml;
 pub use ics::FromIcs;
@@ -25,7 +26,6 @@ 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;
diff --git a/crates/nu-command/src/formats/to/mod.rs b/crates/nu-command/src/formats/to/mod.rs
index 29f6319..18209d3 100644
--- a/crates/nu-command/src/formats/to/mod.rs
+++ b/crates/nu-command/src/formats/to/mod.rs
@@ -8,7 +8,7 @@ mod url;
 
 pub use self::csv::ToCsv;
 pub use self::toml::ToToml;
+pub use self::url::ToUrl;
 pub use command::To;
 pub use json::ToJson;
 pub use tsv::ToTsv;
-pub use url::ToUrl;
diff --git a/crates/nu-command/src/lib.rs b/crates/nu-command/src/lib.rs
index 5933686..14a97fe 100644
--- a/crates/nu-command/src/lib.rs
+++ b/crates/nu-command/src/lib.rs
@@ -10,6 +10,7 @@ mod filesystem;
 mod filters;
 mod formats;
 mod math;
+mod network;
 mod platform;
 mod random;
 mod shells;
@@ -29,6 +30,7 @@ pub use filesystem::*;
 pub use filters::*;
 pub use formats::*;
 pub use math::*;
+pub use network::*;
 pub use platform::*;
 pub use random::*;
 pub use shells::*;
diff --git a/crates/nu-command/src/network/mod.rs b/crates/nu-command/src/network/mod.rs
new file mode 100644
index 0000000..897fc32
--- /dev/null
+++ b/crates/nu-command/src/network/mod.rs
@@ -0,0 +1,3 @@
+mod url;
+
+pub use self::url::*;
diff --git a/crates/nu-command/src/network/url/command.rs b/crates/nu-command/src/network/url/command.rs
new file mode 100644
index 0000000..14cb545
--- /dev/null
+++ b/crates/nu-command/src/network/url/command.rs
@@ -0,0 +1,37 @@
+use nu_engine::get_full_help;
+use nu_protocol::{
+    ast::Call,
+    engine::{Command, EngineState, Stack},
+    Category, IntoPipelineData, PipelineData, Signature, Value,
+};
+
+#[derive(Clone)]
+pub struct Url;
+
+impl Command for Url {
+    fn name(&self) -> &str {
+        "url"
+    }
+
+    fn signature(&self) -> Signature {
+        Signature::build("url").category(Category::Network)
+    }
+
+    fn usage(&self) -> &str {
+        "Apply url function."
+    }
+
+    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(&Url.signature(), &Url.examples(), engine_state),
+            span: call.head,
+        }
+        .into_pipeline_data())
+    }
+}
diff --git a/crates/nu-command/src/network/url/host.rs b/crates/nu-command/src/network/url/host.rs
new file mode 100644
index 0000000..215c268
--- /dev/null
+++ b/crates/nu-command/src/network/url/host.rs
@@ -0,0 +1,65 @@
+use super::{operator, url};
+use nu_protocol::ast::Call;
+use nu_protocol::engine::{Command, EngineState, Stack};
+use nu_protocol::{Category, Example, PipelineData, Signature, Span, SyntaxShape, Value};
+
+#[derive(Clone)]
+pub struct SubCommand;
+
+impl Command for SubCommand {
+    fn name(&self) -> &str {
+        "url host"
+    }
+
+    fn signature(&self) -> Signature {
+        Signature::build("url host")
+            .rest(
+                "rest",
+                SyntaxShape::CellPath,
+                "optionally operate by cell path",
+            )
+            .category(Category::Network)
+    }
+
+    fn usage(&self) -> &str {
+        "gets the host of a url"
+    }
+
+    fn run(
+        &self,
+        engine_state: &EngineState,
+        stack: &mut Stack,
+        call: &Call,
+        input: PipelineData,
+    ) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
+        operator(engine_state, stack, call, input, &host)
+    }
+
+    fn examples(&self) -> Vec<Example> {
+        let span = Span::unknown();
+        vec![Example {
+            description: "Get host of a url",
+            example: "echo 'http://www.example.com/foo/bar' | url host",
+            result: Some(Value::String {
+                val: "www.example.com".to_string(),
+                span,
+            }),
+        }]
+    }
+}
+
+fn host(url: &url::Url) -> &str {
+    url.host_str().unwrap_or("")
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    #[test]
+    fn test_examples() {
+        use crate::test_examples;
+
+        test_examples(SubCommand {})
+    }
+}
diff --git a/crates/nu-command/src/network/url/mod.rs b/crates/nu-command/src/network/url/mod.rs
new file mode 100644
index 0000000..5dcd97b
--- /dev/null
+++ b/crates/nu-command/src/network/url/mod.rs
@@ -0,0 +1,92 @@
+mod command;
+mod host;
+mod path;
+mod query;
+mod scheme;
+
+use nu_engine::CallExt;
+use nu_protocol::{
+    ast::{Call, CellPath},
+    engine::{EngineState, Stack},
+    PipelineData, ShellError, Span, Value,
+};
+use url::{self};
+
+pub use self::host::SubCommand as UrlHost;
+pub use self::path::SubCommand as UrlPath;
+pub use self::query::SubCommand as UrlQuery;
+pub use self::scheme::SubCommand as UrlScheme;
+pub use command::Url;
+
+fn handle_value<F>(action: &F, v: &Value, span: Span) -> Value
+where
+    F: Fn(&url::Url) -> &str + Send + 'static,
+{
+    let a = |url| Value::String {
+        val: action(url).to_string(),
+        span,
+    };
+
+    match v {
+        Value::String { val: s, .. } => {
+            let s = s.trim();
+
+            match url::Url::parse(s) {
+                Ok(url) => a(&url),
+                Err(_) => Value::String {
+                    val: "".to_string(),
+                    span,
+                },
+            }
+        }
+        other => {
+            let span = other.span();
+            match span {
+                Ok(s) => {
+                    let got = format!("Expected a string, got {} instead", other.get_type());
+                    Value::Error {
+                        error: ShellError::UnsupportedInput(got, s),
+                    }
+                }
+                Err(e) => Value::Error { error: e },
+            }
+        }
+    }
+}
+
+fn operator<F>(
+    engine_state: &EngineState,
+    stack: &mut Stack,
+    call: &Call,
+    input: PipelineData,
+    action: &'static F,
+) -> Result<PipelineData, ShellError>
+where
+    F: Fn(&url::Url) -> &str + Send + Sync + 'static,
+{
+    let span = call.head;
+    let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
+
+    input.map(
+        move |v| {
+            if column_paths.is_empty() {
+                handle_value(&action, &v, span)
+            } else {
+                let mut ret = v;
+
+                for path in &column_paths {
+                    let r = ret.update_cell_path(
+                        &path.members,
+                        Box::new(move |old| handle_value(&action, old, span)),
+                    );
+                    if let Err(error) = r {
+                        return Value::Error { error };
+                    }
+                }
+
+                ret
+            }
+        },
+        engine_state.ctrlc.clone(),
+    )
+}
diff --git a/crates/nu-command/src/network/url/path.rs b/crates/nu-command/src/network/url/path.rs
new file mode 100644
index 0000000..c25b285
--- /dev/null
+++ b/crates/nu-command/src/network/url/path.rs
@@ -0,0 +1,71 @@
+use super::{operator, url};
+use nu_protocol::ast::Call;
+use nu_protocol::engine::{Command, EngineState, Stack};
+use nu_protocol::{Category, Example, PipelineData, Signature, Span, SyntaxShape, Value};
+
+#[derive(Clone)]
+pub struct SubCommand;
+
+impl Command for SubCommand {
+    fn name(&self) -> &str {
+        "url path"
+    }
+
+    fn signature(&self) -> Signature {
+        Signature::build("url path")
+            .rest(
+                "rest",
+                SyntaxShape::CellPath,
+                "optionally operate by cell path",
+            )
+            .category(Category::Network)
+    }
+
+    fn usage(&self) -> &str {
+        "gets the path of a url"
+    }
+
+    fn run(
+        &self,
+        engine_state: &EngineState,
+        stack: &mut Stack,
+        call: &Call,
+        input: PipelineData,
+    ) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
+        operator(engine_state, stack, call, input, &url::Url::path)
+    }
+
+    fn examples(&self) -> Vec<Example> {
+        let span = Span::unknown();
+        vec![
+            Example {
+                description: "Get path of a url",
+                example: "echo 'http://www.example.com/foo/bar' | url path",
+                result: Some(Value::String {
+                    val: "/foo/bar".to_string(),
+                    span,
+                }),
+            },
+            Example {
+                description: "A trailing slash will be reflected in the path",
+                example: "echo 'http://www.example.com' | url path",
+                result: Some(Value::String {
+                    val: "/".to_string(),
+                    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/network/url/query.rs b/crates/nu-command/src/network/url/query.rs
new file mode 100644
index 0000000..b5b4c8b
--- /dev/null
+++ b/crates/nu-command/src/network/url/query.rs
@@ -0,0 +1,75 @@
+use super::{operator, url};
+use nu_protocol::ast::Call;
+use nu_protocol::engine::{Command, EngineState, Stack};
+use nu_protocol::{Category, Example, PipelineData, Signature, Span, SyntaxShape, Value};
+
+#[derive(Clone)]
+pub struct SubCommand;
+
+impl Command for SubCommand {
+    fn name(&self) -> &str {
+        "url query"
+    }
+
+    fn signature(&self) -> Signature {
+        Signature::build("url query")
+            .rest(
+                "rest",
+                SyntaxShape::CellPath,
+                "optionally operate by cell path",
+            )
+            .category(Category::Network)
+    }
+
+    fn usage(&self) -> &str {
+        "gets the query of a url"
+    }
+
+    fn run(
+        &self,
+        engine_state: &EngineState,
+        stack: &mut Stack,
+        call: &Call,
+        input: PipelineData,
+    ) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
+        operator(engine_state, stack, call, input, &query)
+    }
+
+    fn examples(&self) -> Vec<Example> {
+        let span = Span::unknown();
+        vec![
+            Example {
+                description: "Get query of a url",
+                example: "echo 'http://www.example.com/?foo=bar&baz=quux' | url query",
+                result: Some(Value::String {
+                    val: "foo=bar&baz=quux".to_string(),
+                    span,
+                }),
+            },
+            Example {
+                description: "No query gives the empty string",
+                example: "echo 'http://www.example.com/' | url query",
+                result: Some(Value::String {
+                    val: "".to_string(),
+                    span,
+                }),
+            },
+        ]
+    }
+}
+
+fn query(url: &url::Url) -> &str {
+    url.query().unwrap_or("")
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    #[test]
+    fn test_examples() {
+        use crate::test_examples;
+
+        test_examples(SubCommand {})
+    }
+}
diff --git a/crates/nu-command/src/network/url/scheme.rs b/crates/nu-command/src/network/url/scheme.rs
new file mode 100644
index 0000000..61c968b
--- /dev/null
+++ b/crates/nu-command/src/network/url/scheme.rs
@@ -0,0 +1,71 @@
+use super::{operator, url};
+use nu_protocol::ast::Call;
+use nu_protocol::engine::{Command, EngineState, Stack};
+use nu_protocol::{Category, Example, PipelineData, Signature, Span, SyntaxShape, Value};
+
+#[derive(Clone)]
+pub struct SubCommand;
+
+impl Command for SubCommand {
+    fn name(&self) -> &str {
+        "url scheme"
+    }
+
+    fn signature(&self) -> Signature {
+        Signature::build("url scheme")
+            .rest(
+                "rest",
+                SyntaxShape::CellPath,
+                "optionally operate by cell path",
+            )
+            .category(Category::Network)
+    }
+
+    fn usage(&self) -> &str {
+        "gets the scheme (eg http, file) of a url"
+    }
+
+    fn run(
+        &self,
+        engine_state: &EngineState,
+        stack: &mut Stack,
+        call: &Call,
+        input: PipelineData,
+    ) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
+        operator(engine_state, stack, call, input, &url::Url::scheme)
+    }
+
+    fn examples(&self) -> Vec<Example> {
+        let span = Span::unknown();
+        vec![
+            Example {
+                description: "Get scheme of a url",
+                example: "echo 'http://www.example.com' | url scheme",
+                result: Some(Value::String {
+                    val: "http".to_string(),
+                    span,
+                }),
+            },
+            Example {
+                description: "You get an empty string if there is no scheme",
+                example: "echo 'test' | url scheme",
+                result: Some(Value::String {
+                    val: "".to_string(),
+                    span,
+                }),
+            },
+        ]
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    #[test]
+    fn test_examples() {
+        use crate::test_examples;
+
+        test_examples(SubCommand {})
+    }
+}
diff --git a/crates/nu-engine/src/documentation.rs b/crates/nu-engine/src/documentation.rs
index 4214340..ed4211e 100644
--- a/crates/nu-engine/src/documentation.rs
+++ b/crates/nu-engine/src/documentation.rs
@@ -273,7 +273,7 @@ fn get_flags_section(signature: &Signature) -> String {
             if let Some(short) = flag.short {
                 if flag.required {
                     format!(
-                        "  -{}{} (required parameter){:?} {}\n",
+                        "  -{}{} (required parameter) {:?} {}\n",
                         short,
                         if !flag.long.is_empty() {
                             format!(", --{}", flag.long)
@@ -298,7 +298,7 @@ fn get_flags_section(signature: &Signature) -> String {
                 }
             } else if flag.required {
                 format!(
-                    "  --{} (required parameter){:?} {}\n",
+                    "  --{} (required parameter) {:?} {}\n",
                     flag.long, arg, flag.desc
                 )
             } else {
diff --git a/crates/nu-protocol/src/config.rs b/crates/nu-protocol/src/config.rs
index 11f67af..9221b21 100644
--- a/crates/nu-protocol/src/config.rs
+++ b/crates/nu-protocol/src/config.rs
@@ -15,7 +15,7 @@ pub struct Config {
     pub animate_prompt: bool,
     pub float_precision: i64,
     pub filesize_format: String,
-    pub without_color: bool,
+    pub use_ansi_coloring: bool,
 }
 
 impl Default for Config {
@@ -30,7 +30,7 @@ impl Default for Config {
             animate_prompt: ANIMATE_PROMPT_DEFAULT,
             float_precision: 4,
             filesize_format: "auto".into(),
-            without_color: false,
+            use_ansi_coloring: true,
         }
     }
 }
@@ -93,8 +93,8 @@ impl Value {
                 "float_precision" => {
                     config.float_precision = value.as_integer()?;
                 }
-                "without_color" => {
-                    config.without_color = value.as_bool()?;
+                "use_ansi_coloring" => {
+                    config.use_ansi_coloring = value.as_bool()?;
                 }
                 "filesize_format" => {
                     config.filesize_format = value.as_string()?.to_lowercase();
diff --git a/crates/nu-protocol/src/signature.rs b/crates/nu-protocol/src/signature.rs
index 0b6b740..4a33766 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,
+    Network,
     Random,
     Platform,
     Shells,
@@ -64,6 +65,7 @@ impl std::fmt::Display for Category {
             Category::Filters => "filters",
             Category::Formats => "formats",
             Category::Math => "math",
+            Category::Network => "network",
             Category::Random => "random",
             Category::Platform => "platform",
             Category::Shells => "shells",
diff --git a/crates/nu-table/src/table.rs b/crates/nu-table/src/table.rs
index e66f829..4ae9cf1 100644
--- a/crates/nu-table/src/table.rs
+++ b/crates/nu-table/src/table.rs
@@ -954,7 +954,7 @@ impl WrappedTable {
         }
 
         // the atty is for when people do ls from vim, there should be no coloring there
-        if config.without_color || !atty::is(atty::Stream::Stdout) {
+        if !config.use_ansi_coloring || !atty::is(atty::Stream::Stdout) {
             // Draw the table without ansi colors
             if let Ok(bytes) = strip_ansi_escapes::strip(&output) {
                 String::from_utf8_lossy(&bytes).to_string()
diff --git a/src/main.rs b/src/main.rs
index 74a3664..abb6618 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -309,7 +309,10 @@ fn main() -> Result<()> {
                 // ))
                 .with_validator(Box::new(NuValidator {
                     engine_state: engine_state.clone(),
-                }));
+                }))
+                .with_ansi_colors(config.use_ansi_coloring);
+            //FIXME: if config.use_ansi_coloring is false then we should
+            // turn off the hinter but I don't see any way to do that yet.
 
             let mut line_editor = if let Some(history_path) = history_path.clone() {
                 line_editor


Test patch:
diff --git a/crates/nu-command/src/example_test.rs b/crates/nu-command/src/example_test.rs
index da5789c..ed7e951 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, Random, Split, Str};
+use super::{Date, From, Into, Math, Random, Split, Str, Url};
 
 pub fn test_examples(cmd: impl Command + 'static) {
     let examples = cmd.examples();
@@ -25,6 +25,7 @@ pub fn test_examples(cmd: impl Command + 'static) {
         working_set.add_decl(Box::new(Split));
         working_set.add_decl(Box::new(Math));
         working_set.add_decl(Box::new(Date));
+        working_set.add_decl(Box::new(Url));
 
         use super::Echo;
         working_set.add_decl(Box::new(Echo));