Repository: nushell/engine-q
Pull request #452 (2021-12-08 17:27:44)

Port `network/url` command
Porting url commands!

Because url commands use generic `operator` function, I didn't create sub-commands with different PRs.


Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 2fe8851..2dff56a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -103,6 +103,15 @@ version = "0.3.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544"
 
+[[package]]
+name = "arrayvec"
+version = "0.4.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9"
+dependencies = [
+ "nodrop",
+]
+
 [[package]]
 name = "arrayvec"
 version = "0.5.2"
@@ -269,7 +278,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "afa748e348ad3be8263be728124b24a24f268266f6f5d58af9d75f6a40b5c587"
 dependencies = [
  "arrayref",
- "arrayvec",
+ "arrayvec 0.5.2",
  "constant_time_eq",
 ]
 
@@ -306,6 +315,12 @@ dependencies = [
  "serde",
 ]
 
+[[package]]
+name = "bumpalo"
+version = "3.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f1e260c3a9040a7c19a12468758f4c16f31a81a1fe087482be9570ec864bb6c"
+
 [[package]]
 name = "byte-unit"
 version = "4.0.13"
@@ -548,6 +563,16 @@ version = "0.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
 
+[[package]]
+name = "cstr_core"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "917ba9efe9e1e736671d5a03f006afc4e7e3f32503e2077e0bcaf519c0c8c1d3"
+dependencies = [
+ "cty",
+ "memchr",
+]
+
 [[package]]
 name = "csv"
 version = "1.1.6"
@@ -590,6 +615,12 @@ dependencies = [
  "winapi",
 ]
 
+[[package]]
+name = "cty"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35"
+
 [[package]]
 name = "dialoguer"
 version = "0.9.0"
@@ -1137,6 +1168,15 @@ dependencies = [
  "libc",
 ]
 
+[[package]]
+name = "js-sys"
+version = "0.3.55"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84"
+dependencies = [
+ "wasm-bindgen",
+]
+
 [[package]]
 name = "lazy_static"
 version = "1.4.0"
@@ -1455,6 +1495,12 @@ dependencies = [
  "memoffset",
 ]
 
+[[package]]
+name = "nodrop"
+version = "0.1.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb"
+
 [[package]]
 name = "nom"
 version = "1.2.4"
@@ -1564,6 +1610,7 @@ dependencies = [
  "toml",
  "trash",
  "unicode-segmentation",
+ "url",
  "uuid",
 ]
 
@@ -1631,8 +1678,10 @@ dependencies = [
  "indexmap",
  "miette",
  "nu-json",
+ "num-format",
  "serde",
  "serde_json",
+ "sys-locale",
  "thiserror",
  "typetag",
 ]
@@ -1642,6 +1691,7 @@ name = "nu-table"
 version = "0.36.0"
 dependencies = [
  "ansi-cut",
+ "atty",
  "nu-ansi-term 0.39.0",
  "nu-protocol",
  "regex",
@@ -1753,6 +1803,16 @@ dependencies = [
  "num-traits",
 ]
 
+[[package]]
+name = "num-format"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bafe4179722c2894288ee77a9f044f02811c86af699344c498b0840c698a2465"
+dependencies = [
+ "arrayvec 0.4.12",
+ "itoa",
+]
+
 [[package]]
 name = "num-integer"
 version = "0.1.44"
@@ -2309,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",
 ]
@@ -2659,6 +2720,19 @@ dependencies = [
  "unicode-xid",
 ]
 
+[[package]]
+name = "sys-locale"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91f89ebb59fa30d4f65fafc2d68e94f6975256fd87e812dd99cb6e020c8563df"
+dependencies = [
+ "cc",
+ "cstr_core",
+ "libc",
+ "web-sys",
+ "winapi",
+]
+
 [[package]]
 name = "sysinfo"
 version = "0.20.5"
@@ -2966,7 +3040,7 @@ version = "0.10.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6cbce692ab4ca2f1f3047fcf732430249c0e971bfdd2b234cf2c47ad93af5983"
 dependencies = [
- "arrayvec",
+ "arrayvec 0.5.2",
  "utf8parse",
  "vte_generate_state_changes",
 ]
@@ -3002,6 +3076,70 @@ version = "0.10.0+wasi-snapshot-preview1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
 
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.78"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce"
+dependencies = [
+ "cfg-if",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.78"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b"
+dependencies = [
+ "bumpalo",
+ "lazy_static",
+ "log",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.78"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.78"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.78"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc"
+
+[[package]]
+name = "web-sys"
+version = "0.3.55"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38eb105f1c59d9eaa6b5cdc92b859d85b926e82cb2e0945cd0c9259faa6fe9fb"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
 [[package]]
 name = "winapi"
 version = "0.3.9"
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/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-protocol/Cargo.toml b/crates/nu-protocol/Cargo.toml
index 179518b..51749f5 100644
--- a/crates/nu-protocol/Cargo.toml
+++ b/crates/nu-protocol/Cargo.toml
@@ -17,6 +17,8 @@ im = "15.0.0"
 serde_json = { version = "1.0", optional = true }
 nu-json = { path = "../nu-json" }
 typetag = "0.1.8"
+num-format = "0.4.0"
+sys-locale = "0.1.0"
 
 [features]
 plugin = ["serde_json"]
diff --git a/crates/nu-protocol/src/config.rs b/crates/nu-protocol/src/config.rs
index fbbc488..9221b21 100644
--- a/crates/nu-protocol/src/config.rs
+++ b/crates/nu-protocol/src/config.rs
@@ -14,6 +14,8 @@ pub struct Config {
     pub footer_mode: FooterMode,
     pub animate_prompt: bool,
     pub float_precision: i64,
+    pub filesize_format: String,
+    pub use_ansi_coloring: bool,
 }
 
 impl Default for Config {
@@ -27,6 +29,8 @@ impl Default for Config {
             footer_mode: FooterMode::Never,
             animate_prompt: ANIMATE_PROMPT_DEFAULT,
             float_precision: 4,
+            filesize_format: "auto".into(),
+            use_ansi_coloring: true,
         }
     }
 }
@@ -72,7 +76,7 @@ impl Value {
                     config.use_grid_icons = value.as_bool()?;
                 }
                 "footer_mode" => {
-                    let val_str = value.as_string()?;
+                    let val_str = value.as_string()?.to_lowercase();
                     config.footer_mode = match val_str.as_ref() {
                         "auto" => FooterMode::Auto,
                         "never" => FooterMode::Never,
@@ -84,12 +88,16 @@ impl Value {
                     };
                 }
                 "animate_prompt" => {
-                    let val = value.as_bool()?;
-                    config.animate_prompt = val;
+                    config.animate_prompt = value.as_bool()?;
                 }
                 "float_precision" => {
-                    let val = value.as_integer()?;
-                    config.float_precision = val;
+                    config.float_precision = value.as_integer()?;
+                }
+                "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-protocol/src/value/mod.rs b/crates/nu-protocol/src/value/mod.rs
index 6abaf6e..a64ac7e 100644
--- a/crates/nu-protocol/src/value/mod.rs
+++ b/crates/nu-protocol/src/value/mod.rs
@@ -5,13 +5,16 @@ mod range;
 mod stream;
 mod unit;
 
+use byte_unit::ByteUnit;
 use chrono::{DateTime, FixedOffset};
 use chrono_humanize::HumanTime;
 pub use from_value::FromValue;
 use indexmap::map::IndexMap;
+use num_format::{Locale, ToFormattedString};
 pub use range::*;
 use serde::{Deserialize, Serialize};
 pub use stream::*;
+use sys_locale::get_locale;
 pub use unit::*;
 
 use std::collections::HashMap;
@@ -1474,16 +1477,114 @@ pub fn format_duration(duration: i64) -> String {
 }
 
 fn format_filesize(num_bytes: i64, config: &Config) -> String {
-    let byte = byte_unit::Byte::from_bytes(num_bytes as u128);
+    // Allow the user to specify how they want their numbers formatted
+    let filesize_format_var = get_config_filesize_format(config);
 
-    if byte.get_bytes() == 0u128 {
-        return "—".to_string();
+    let byte = byte_unit::Byte::from_bytes(num_bytes as u128);
+    let adj_byte =
+        if filesize_format_var.0 == byte_unit::ByteUnit::B && filesize_format_var.1 == "auto" {
+            byte.get_appropriate_unit(!config.filesize_metric)
+        } else {
+            byte.get_adjusted_unit(filesize_format_var.0)
+        };
+
+    match adj_byte.get_unit() {
+        byte_unit::ByteUnit::B => {
+            let locale_string = get_locale().unwrap_or_else(|| String::from("en-US"));
+            // Since get_locale() and Locale::from_name() don't always return the same items
+            // we need to try and parse it to match. For instance, a valid locale is de_DE
+            // however Locale::from_name() wants only de so we split and parse it out.
+            let locale_string = locale_string.replace("_", "-"); // en_AU -> en-AU
+            let locale = match Locale::from_name(&locale_string) {
+                Ok(loc) => loc,
+                _ => {
+                    let all = num_format::Locale::available_names();
+                    let locale_prefix = &locale_string.split('-').collect::<Vec<&str>>();
+                    if all.contains(&locale_prefix[0]) {
+                        // eprintln!("Found alternate: {}", &locale_prefix[0]);
+                        Locale::from_name(locale_prefix[0]).unwrap_or(Locale::en)
+                    } else {
+                        // eprintln!("Unable to find matching locale. Defaulting to en-US");
+                        Locale::en
+                    }
+                }
+            };
+            let locale_byte = adj_byte.get_value() as u64;
+            let locale_byte_string = locale_byte.to_formatted_string(&locale);
+
+            if filesize_format_var.1 == "auto" {
+                format!("{} B", locale_byte_string)
+            } else {
+                locale_byte_string
+            }
+        }
+        _ => adj_byte.format(1),
     }
+}
 
-    let byte = byte.get_appropriate_unit(config.filesize_metric);
+fn get_config_filesize_format(config: &Config) -> (ByteUnit, &str) {
+    // We need to take into account config.filesize_metric so, if someone asks for KB
+    // filesize_metric is true, return KiB
+    let filesize_format = match config.filesize_format.as_str() {
+        "b" => (byte_unit::ByteUnit::B, ""),
+        "kb" => {
+            if config.filesize_metric {
+                (byte_unit::ByteUnit::KiB, "")
+            } else {
+                (byte_unit::ByteUnit::KB, "")
+            }
+        }
+        "kib" => (byte_unit::ByteUnit::KiB, ""),
+        "mb" => {
+            if config.filesize_metric {
+                (byte_unit::ByteUnit::MiB, "")
+            } else {
+                (byte_unit::ByteUnit::MB, "")
+            }
+        }
+        "mib" => (byte_unit::ByteUnit::MiB, ""),
+        "gb" => {
+            if config.filesize_metric {
+                (byte_unit::ByteUnit::GiB, "")
+            } else {
+                (byte_unit::ByteUnit::GB, "")
+            }
+        }
+        "gib" => (byte_unit::ByteUnit::GiB, ""),
+        "tb" => {
+            if config.filesize_metric {
+                (byte_unit::ByteUnit::TiB, "")
+            } else {
+                (byte_unit::ByteUnit::TB, "")
+            }
+        }
+        "tib" => (byte_unit::ByteUnit::TiB, ""),
+        "pb" => {
+            if config.filesize_metric {
+                (byte_unit::ByteUnit::PiB, "")
+            } else {
+                (byte_unit::ByteUnit::PB, "")
+            }
+        }
+        "pib" => (byte_unit::ByteUnit::PiB, ""),
+        "eb" => {
+            if config.filesize_metric {
+                (byte_unit::ByteUnit::EiB, "")
+            } else {
+                (byte_unit::ByteUnit::EB, "")
+            }
+        }
+        "eib" => (byte_unit::ByteUnit::EiB, ""),
+        "zb" => {
+            if config.filesize_metric {
+                (byte_unit::ByteUnit::ZiB, "")
+            } else {
+                (byte_unit::ByteUnit::ZB, "")
+            }
+        }
+        "zib" => (byte_unit::ByteUnit::ZiB, ""),
+        _ => (byte_unit::ByteUnit::B, "auto"),
+    };
 
-    match byte.get_unit() {
-        byte_unit::ByteUnit::B => format!("{} B ", byte.get_value()),
-        _ => byte.format(1),
-    }
+    filesize_format
 }
diff --git a/crates/nu-table/Cargo.toml b/crates/nu-table/Cargo.toml
index 18cb076..6138139 100644
--- a/crates/nu-table/Cargo.toml
+++ b/crates/nu-table/Cargo.toml
@@ -12,10 +12,10 @@ name = "table"
 path = "src/main.rs"
 
 [dependencies]
-# nu-ansi-term = "0.39.0"
 nu-ansi-term = { path = "../nu-ansi-term" }
 nu-protocol = { path = "../nu-protocol"}
 regex = "1.4"
 unicode-width = "0.1.8"
 strip-ansi-escapes = "0.1.1"
-ansi-cut = "0.1.1"
\ No newline at end of file
+ansi-cut = "0.1.1"
+atty = "0.2.14"
diff --git a/crates/nu-table/src/table.rs b/crates/nu-table/src/table.rs
index e73f24e..4ae9cf1 100644
--- a/crates/nu-table/src/table.rs
+++ b/crates/nu-table/src/table.rs
@@ -883,10 +883,11 @@ impl WrappedTable {
     fn print_table(&self, color_hm: &HashMap<String, Style>, config: &Config) -> String {
         let mut output = String::new();
 
-        #[cfg(windows)]
-        {
-            let _ = nu_ansi_term::enable_ansi_support();
-        }
+        // TODO: This may be unnecessary after JTs changes. Let's remove it and see.
+        // #[cfg(windows)]
+        // {
+        //     let _ = nu_ansi_term::enable_ansi_support();
+        // }
 
         if self.data.is_empty() {
             return output;
@@ -952,7 +953,18 @@ impl WrappedTable {
             output.push_str(&self.print_separator(SeparatorPosition::Bottom, color_hm));
         }
 
-        output
+        // the atty is for when people do ls from vim, there should be no coloring there
+        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()
+            } else {
+                output
+            }
+        } else {
+            // Draw the table with ansi colors
+            output
+        }
     }
 }
 
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));
diff --git a/src/tests.rs b/src/tests.rs
index 4b9754b..f8be638 100644
--- a/src/tests.rs
+++ b/src/tests.rs
@@ -1148,19 +1148,28 @@ fn multi_word_imports() -> TestResult {
 }
 
 #[test]
-fn config_var_1() -> TestResult {
+fn config_filesize_format_with_metric_true() -> TestResult {
     // Note: this tests both the config variable and that it is properly captured into a block
     run_test(
-        r#"let config = {"filesize_metric": $true }; do { 40kb | into string } "#,
+        r#"let config = {"filesize_metric": $true "filesize_format": "kib" }; do { 40kb | into string } "#,
         "39.1 KiB",
     )
 }
 
 #[test]
-fn config_var_2() -> TestResult {
+fn config_filesize_format_with_metric_false_kib() -> TestResult {
     // Note: this tests both the config variable and that it is properly captured into a block
     run_test(
-        r#"let config = {"filesize_metric": $false }; do { 40kb | into string } "#,
+        r#"let config = {"filesize_metric": $false "filesize_format": "kib" }; do { 40kb | into string } "#,
+        "39.1 KiB",
+    )
+}
+
+#[test]
+fn config_filesize_format_with_metric_false_kb() -> TestResult {
+    // Note: this tests both the config variable and that it is properly captured into a block
+    run_test(
+        r#"let config = {"filesize_metric": $false "filesize_format": "kb" }; do { 40kb | into string } "#,
         "40.0 KB",
     )
 }