Merge remote-tracking branch 'upstream/master' into direnv-rewrite

This commit is contained in:
Sam Hedin 2020-06-22 16:42:54 +02:00
commit 0215e4c1b6
81 changed files with 3459 additions and 1519 deletions

361
Cargo.lock generated
View File

@ -24,6 +24,15 @@ dependencies = [
"memchr",
]
[[package]]
name = "ansi_colours"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d0f302a81afc6a7f4350c04f0ba7cfab529cc009bca3324b3fb5764e6add8b6"
dependencies = [
"cc",
]
[[package]]
name = "ansi_term"
version = "0.11.0"
@ -42,6 +51,12 @@ dependencies = [
"winapi 0.3.8",
]
[[package]]
name = "anymap"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33954243bd79057c2de7338850b85983a44588021f8a5fee574a8888c6de4344"
[[package]]
name = "app_dirs"
version = "1.2.1"
@ -212,6 +227,35 @@ version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53d1ccbaf7d9ec9537465a97bf19edc1a4e158ecb49fc16178202238c569cc42"
[[package]]
name = "bat"
version = "0.15.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91f17c2d9e1cee447a788a15fa6819c0cb488fb2935e3e8c4e7120e1678b7aa8"
dependencies = [
"ansi_colours",
"ansi_term 0.12.1",
"atty",
"clap",
"console",
"content_inspector",
"dirs 2.0.2",
"encoding",
"error-chain",
"git2",
"globset",
"lazy_static 1.4.0",
"liquid",
"path_abs",
"semver 0.9.0",
"serde 1.0.110",
"serde_yaml",
"shell-words",
"syntect",
"unicode-width",
"wild",
]
[[package]]
name = "battery"
version = "0.7.5"
@ -433,6 +477,7 @@ dependencies = [
"atty",
"bitflags",
"strsim",
"term_size",
"textwrap",
"unicode-width",
"vec_map",
@ -505,12 +550,38 @@ dependencies = [
"yaml-rust",
]
[[package]]
name = "console"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c0994e656bba7b922d8dd1245db90672ffb701e684e45be58f20719d69abc5a"
dependencies = [
"encode_unicode",
"lazy_static 1.4.0",
"libc",
"regex",
"terminal_size",
"termios",
"unicode-width",
"winapi 0.3.8",
"winapi-util",
]
[[package]]
name = "constant_time_eq"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
[[package]]
name = "content_inspector"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7bda66e858c683005a53a9a60c69a4aca7eeaa45d124526e389f7aec8e62f38"
dependencies = [
"memchr",
]
[[package]]
name = "core-foundation"
version = "0.6.4"
@ -914,6 +985,70 @@ version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
[[package]]
name = "encoding"
version = "0.2.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b0d943856b990d12d3b55b359144ff341533e516d94098b1d3fc1ac666d36ec"
dependencies = [
"encoding-index-japanese",
"encoding-index-korean",
"encoding-index-simpchinese",
"encoding-index-singlebyte",
"encoding-index-tradchinese",
]
[[package]]
name = "encoding-index-japanese"
version = "1.20141219.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04e8b2ff42e9a05335dbf8b5c6f7567e5591d0d916ccef4e0b1710d32a0d0c91"
dependencies = [
"encoding_index_tests",
]
[[package]]
name = "encoding-index-korean"
version = "1.20141219.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4dc33fb8e6bcba213fe2f14275f0963fd16f0a02c878e3095ecfdf5bee529d81"
dependencies = [
"encoding_index_tests",
]
[[package]]
name = "encoding-index-simpchinese"
version = "1.20141219.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d87a7194909b9118fc707194baa434a4e3b0fb6a5a757c73c3adb07aa25031f7"
dependencies = [
"encoding_index_tests",
]
[[package]]
name = "encoding-index-singlebyte"
version = "1.20141219.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3351d5acffb224af9ca265f435b859c7c01537c0849754d3db3fdf2bfe2ae84a"
dependencies = [
"encoding_index_tests",
]
[[package]]
name = "encoding-index-tradchinese"
version = "1.20141219.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd0e20d5688ce3cab59eb3ef3a2083a5c77bf496cb798dc6fcdb75f323890c18"
dependencies = [
"encoding_index_tests",
]
[[package]]
name = "encoding_index_tests"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a246d82be1c9d791c5dfde9a2bd045fc3cbba3fa2b11ad558f27d01712f00569"
[[package]]
name = "encoding_rs"
version = "0.8.23"
@ -945,6 +1080,15 @@ dependencies = [
"serde 1.0.110",
]
[[package]]
name = "error-chain"
version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d371106cc88ffdfb1eabd7111e432da544f16f3e2d7bf1dfe8bf575f1df045cd"
dependencies = [
"version_check 0.9.1",
]
[[package]]
name = "failure"
version = "0.1.8"
@ -1335,10 +1479,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
[[package]]
name = "heim"
version = "0.1.0-beta.2"
name = "globset"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea9164f267a5f4325020b8a989c4b0ab06acc0685ccdb22551f59257fdf296ab"
checksum = "7ad1da430bd7281dde2576f44c84cc3f0f7b475e7202cd503042dff01a8c8120"
dependencies = [
"aho-corasick",
"bstr",
"fnv",
"log",
"regex",
]
[[package]]
name = "heim"
version = "0.1.0-beta.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1014732324a9baf5a691525faabb33909bf6f40dcc2b03c8f2fb07bb01e7e3f"
dependencies = [
"heim-common",
"heim-cpu",
@ -1372,9 +1529,9 @@ dependencies = [
[[package]]
name = "heim-cpu"
version = "0.1.0-beta.2"
version = "0.1.0-beta.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b088c42ce30cf60b485df484e0aa19c31ad8663bb939180ef64ca340d15eca6"
checksum = "73b1442359831aa671aa931f0a084aab210e77b1330ded78f1e60cc305abc4bb"
dependencies = [
"cfg-if",
"futures 0.3.5",
@ -1456,9 +1613,9 @@ dependencies = [
[[package]]
name = "heim-process"
version = "0.1.1-beta.2"
version = "0.1.1-beta.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "190f1085293c8d54060dd77c943da0d5bd1729aa00d2ac68188e26446dc0170d"
checksum = "fd969deb2a89a488b6a9bf18a65923ae4cdef6b128fa2dedb74ef5c694deb5ae"
dependencies = [
"async-trait",
"cfg-if",
@ -1740,6 +1897,15 @@ dependencies = [
"winapi-build",
]
[[package]]
name = "kstring"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbbc30beb80d56ddf6346e935c7abcba96329ee5c5a4cde8984a4e6b6f18b58e"
dependencies = [
"serde 1.0.110",
]
[[package]]
name = "kv-log-macro"
version = "1.0.5"
@ -1856,6 +2022,64 @@ version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8dd5a6d5999d9907cda8ed67bbd137d3af8085216c2ac62de5be860bd41f304a"
[[package]]
name = "liquid"
version = "0.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "503b7cd741bf1a6c01bfdf697ba13f67e2c8e152920af25596763bb0dbcd6215"
dependencies = [
"doc-comment",
"kstring",
"liquid-core",
"liquid-derive",
"liquid-lib",
"serde 1.0.110",
]
[[package]]
name = "liquid-core"
version = "0.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4dc58422728185d54cd044bba4d45a2ef2a7111a421f84d344f65629949de4f1"
dependencies = [
"anymap",
"chrono",
"itertools",
"kstring",
"liquid-derive",
"num-traits 0.2.11",
"pest",
"pest_derive",
"serde 1.0.110",
]
[[package]]
name = "liquid-derive"
version = "0.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfef35f37f019e5dfc550517045078317f5d37afa64cbf246ecde616a7091cb0"
dependencies = [
"proc-macro2",
"proc-quote",
"syn",
]
[[package]]
name = "liquid-lib"
version = "0.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c4aa47dc08fd8c6c8aea70a0da2a98c0f0416d49e8b03c5c46354ef559bee3c"
dependencies = [
"chrono",
"itertools",
"kstring",
"liquid-core",
"once_cell",
"percent-encoding",
"regex",
"unicode-segmentation",
]
[[package]]
name = "lock_api"
version = "0.3.4"
@ -2248,6 +2472,7 @@ dependencies = [
"nu-plugin",
"nu-protocol",
"nu-source",
"nu-table",
"nu-test-support",
"nu-value-ext",
"num-bigint",
@ -2256,7 +2481,6 @@ dependencies = [
"pin-utils",
"pretty-hex",
"pretty_env_logger",
"prettytable-rs",
"ptree",
"query_interface",
"quickcheck",
@ -2387,6 +2611,14 @@ dependencies = [
"termcolor",
]
[[package]]
name = "nu-table"
version = "0.15.1"
dependencies = [
"ansi_term 0.12.1",
"unicode-width",
]
[[package]]
name = "nu-test-support"
version = "0.15.1"
@ -2540,13 +2772,16 @@ name = "nu_plugin_textview"
version = "0.15.1"
dependencies = [
"ansi_term 0.12.1",
"bat",
"crossterm",
"nu-build",
"nu-cli",
"nu-errors",
"nu-plugin",
"nu-protocol",
"nu-source",
"syntect",
"textwrap",
"url",
]
@ -2686,6 +2921,28 @@ version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b631f7e854af39a1739f401cf34a8a013dfe09eac4fa4dba91e9768bd28168d"
[[package]]
name = "onig"
version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd91ccd8a02fce2f7e8a86655aec67bc6c171e6f8e704118a0e8c4b866a05a8a"
dependencies = [
"bitflags",
"lazy_static 1.4.0",
"libc",
"onig_sys",
]
[[package]]
name = "onig_sys"
version = "69.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3814583fad89f3c60ae0701d80e87e1fd3028741723deda72d0d4a0ecf0cb0db"
dependencies = [
"cc",
"pkg-config",
]
[[package]]
name = "opaque-debug"
version = "0.2.3"
@ -2790,6 +3047,15 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0858af4d9136275541f4eac7be1af70add84cf356d901799b065ac1b8ff6e2f"
[[package]]
name = "path_abs"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb6b8e6dede0bf94e9300e669f335ba92d5fc9fc8be7f4b1ca8a05206489388c"
dependencies = [
"std_prelude",
]
[[package]]
name = "percent-encoding"
version = "2.1.0"
@ -2968,20 +3234,6 @@ dependencies = [
"log",
]
[[package]]
name = "prettytable-rs"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fd04b170004fa2daccf418a7f8253aaf033c27760b5f225889024cf66d7ac2e"
dependencies = [
"atty",
"csv",
"encode_unicode",
"lazy_static 1.4.0",
"term",
"unicode-width",
]
[[package]]
name = "proc-macro-error"
version = "1.0.2"
@ -3029,6 +3281,30 @@ dependencies = [
"unicode-xid",
]
[[package]]
name = "proc-quote"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06ea4226882439d07839be9c7f683e13d6d69d9c2fe960d61f637d1e2fa4c081"
dependencies = [
"proc-macro-hack",
"proc-macro2",
"proc-quote-impl",
"quote",
"syn",
]
[[package]]
name = "proc-quote-impl"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fb3ec628b063cdbcf316e06a8b8c1a541d28fa6c0a8eacd2bfb2b7f49e88aa0"
dependencies = [
"proc-macro-hack",
"proc-macro2",
"quote",
]
[[package]]
name = "ptree"
version = "0.2.1"
@ -3575,6 +3851,12 @@ dependencies = [
"opaque-debug",
]
[[package]]
name = "shell-words"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6fa3938c99da4914afedd13bf3d79bcb6c277d1b2c398d23257a304d9e1b074"
[[package]]
name = "shell32-sys"
version = "0.1.2"
@ -3724,6 +4006,12 @@ version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f3eb36b47e512f8f1c9e3d10c2c1965bc992bd9cdb024fa581e2194501c83d3"
[[package]]
name = "std_prelude"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8207e78455ffdf55661170876f88daf85356e4edd54e0a3dbc79586ca1e50cbe"
[[package]]
name = "strip-ansi-escapes"
version = "0.1.0"
@ -3808,6 +4096,7 @@ dependencies = [
"fnv",
"lazy_static 1.4.0",
"lazycell",
"onig",
"plist",
"regex-syntax",
"serde 1.0.110",
@ -3876,6 +4165,25 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "terminal_size"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8038f95fc7a6f351163f4b964af631bd26c9e828f7db085f2a84aca56f70d13b"
dependencies = [
"libc",
"winapi 0.3.8",
]
[[package]]
name = "termios"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f0fcee7b24a25675de40d5bb4de6e41b0df07bc9856295e7e2b3a3600c400c2"
dependencies = [
"libc",
]
[[package]]
name = "textwrap"
version = "0.11.0"
@ -4323,6 +4631,15 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "effc0e4ff8085673ea7b9b2e3c73f6bd4d118810c9009ed8f1e16bd96c331db6"
[[package]]
name = "wild"
version = "2.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "035793abb854745033f01a07647a79831eba29ec0be377205f2a25b0aa830020"
dependencies = [
"glob",
]
[[package]]
name = "winapi"
version = "0.2.8"

View File

@ -17,6 +17,7 @@ nu-errors = { version = "0.15.1", path = "../nu-errors" }
nu-parser = { version = "0.15.1", path = "../nu-parser" }
nu-value-ext = { version = "0.15.1", path = "../nu-value-ext" }
nu-test-support = { version = "0.15.1", path = "../nu-test-support" }
nu-table = {version = "0.15.1", path = "../nu-table"}
ansi_term = "0.12.1"
app_dirs = "1.2.1"
@ -61,7 +62,6 @@ parking_lot = "0.10.2"
pin-utils = "0.1.0"
pretty-hex = "0.1.1"
pretty_env_logger = "0.4.0"
prettytable-rs = "0.8.0"
ptree = {version = "0.2" }
query_interface = "0.3.5"
rand = "0.7"

View File

@ -49,8 +49,9 @@ fn load_plugin(path: &std::path::Path, context: &mut Context) -> Result<(), Shel
let mut input = String::new();
let result = match reader.read_line(&mut input) {
Ok(count) => {
trace!("processing response ({} bytes)", count);
trace!("response: {}", input);
trace!(target: "nu::load", "plugin infrastructure -> config response");
trace!(target: "nu::load", "plugin infrastructure -> processing response ({} bytes)", count);
trace!(target: "nu::load", "plugin infrastructure -> response: {}", input);
let response = serde_json::from_str::<JsonRpc<Result<Signature, ShellError>>>(&input);
match response {
@ -58,13 +59,13 @@ fn load_plugin(path: &std::path::Path, context: &mut Context) -> Result<(), Shel
Ok(params) => {
let fname = path.to_string_lossy();
trace!("processing {:?}", params);
trace!(target: "nu::load", "plugin infrastructure -> processing {:?}", params);
let name = params.name.clone();
let fname = fname.to_string();
if context.get_command(&name).is_some() {
trace!("plugin {:?} already loaded.", &name);
trace!(target: "nu::load", "plugin infrastructure -> {:?} already loaded.", &name);
} else if params.is_filter {
context.add_commands(vec![whole_stream_command(PluginCommand::new(
name, fname, params,
@ -79,7 +80,7 @@ fn load_plugin(path: &std::path::Path, context: &mut Context) -> Result<(), Shel
Err(e) => Err(e),
},
Err(e) => {
trace!("incompatible plugin {:?}", input);
trace!(target: "nu::load", "plugin infrastructure -> incompatible {:?}", input);
Err(ShellError::untagged_runtime_error(format!(
"Error: {:?}",
e
@ -188,7 +189,7 @@ pub fn load_plugins(context: &mut Context) -> Result<(), ShellError> {
};
if is_valid_name && is_executable {
trace!("Trying {:?}", path.display());
trace!(target: "nu::load", "plugin infrastructure -> Trying {:?}", path.display());
// we are ok if this plugin load fails
let _ = load_plugin(&path, &mut context.clone());
@ -320,6 +321,7 @@ pub fn create_default_context(
whole_stream_command(GroupByDate),
whole_stream_command(First),
whole_stream_command(Last),
whole_stream_command(Every),
whole_stream_command(Nth),
whole_stream_command(Drop),
whole_stream_command(Format),
@ -349,10 +351,11 @@ pub fn create_default_context(
whole_stream_command(AutoenvTrust),
whole_stream_command(AutoenvUnTrust),
whole_stream_command(Math),
whole_stream_command(Average),
whole_stream_command(Minimum),
whole_stream_command(Maximum),
whole_stream_command(Sum),
whole_stream_command(MathAverage),
whole_stream_command(MathMedian),
whole_stream_command(MathMinimum),
whole_stream_command(MathMaximum),
whole_stream_command(MathSummation),
// File format output
whole_stream_command(To),
whole_stream_command(ToBSON),
@ -739,7 +742,7 @@ fn chomp_newline(s: &str) -> &str {
}
}
enum LineResult {
pub enum LineResult {
Success(String),
Error(String, ShellError),
CtrlC,
@ -747,7 +750,7 @@ enum LineResult {
}
/// Process the line by parsing the text to turn it into commands, classify those commands so that we understand what is being called in the pipeline, and then run this pipeline
async fn process_line(
pub async fn process_line(
readline: Result<String, ReadlineError>,
ctx: &mut Context,
redirect_stdin: bool,

View File

@ -33,6 +33,7 @@ pub(crate) mod echo;
pub(crate) mod enter;
#[allow(unused)]
pub(crate) mod evaluate_by;
pub(crate) mod every;
pub(crate) mod exit;
pub(crate) mod first;
pub(crate) mod format;
@ -104,7 +105,6 @@ pub(crate) mod sort_by;
pub(crate) mod split;
pub(crate) mod split_by;
pub(crate) mod str_;
pub(crate) mod sum;
#[allow(unused)]
pub(crate) mod t_sort_by;
pub(crate) mod table;
@ -156,7 +156,6 @@ pub(crate) use du::Du;
pub(crate) use each::Each;
pub(crate) use echo::Echo;
pub(crate) use is_empty::IsEmpty;
pub(crate) use math::Math;
pub(crate) use update::Update;
pub(crate) mod kill;
pub(crate) use kill::Kill;
@ -166,6 +165,7 @@ pub(crate) mod touch;
pub(crate) use enter::Enter;
#[allow(unused_imports)]
pub(crate) use evaluate_by::EvaluateBy;
pub(crate) use every::Every;
pub(crate) use exit::Exit;
pub(crate) use first::First;
pub(crate) use format::Format;
@ -204,7 +204,7 @@ pub(crate) use lines::Lines;
pub(crate) use ls::Ls;
#[allow(unused_imports)]
pub(crate) use map_max_by::MapMaxBy;
pub(crate) use math::{Average, Maximum, Minimum};
pub(crate) use math::{Math, MathAverage, MathMaximum, MathMedian, MathMinimum, MathSummation};
pub(crate) use merge::Merge;
pub(crate) use mkdir::Mkdir;
pub(crate) use mv::Move;
@ -241,7 +241,6 @@ pub(crate) use str_::{
Str, StrCapitalize, StrDowncase, StrFindReplace, StrSet, StrSubstring, StrToDatetime,
StrToDecimal, StrToInteger, StrTrim, StrUpcase,
};
pub(crate) use sum::Sum;
#[allow(unused_imports)]
pub(crate) use t_sort_by::TSortBy;
pub(crate) use table::Table;

View File

@ -6,10 +6,7 @@ use nu_errors::ShellError;
use nu_protocol::{hir, hir::Expression, hir::Literal, hir::SpannedExpression};
use nu_protocol::{Primitive, Scope, Signature, UntaggedValue, Value};
use parking_lot::Mutex;
use prettytable::format::{FormatBuilder, LinePosition, LineSeparator};
use prettytable::{color, Attr, Cell, Row, Table};
use std::sync::atomic::AtomicBool;
use textwrap::fill;
pub struct Autoview;
@ -268,90 +265,28 @@ pub async fn autoview(context: RunnableContext) -> Result<OutputStream, ShellErr
+ row.entries.iter().count() * 2)
> textwrap::termwidth()) =>
{
let termwidth = std::cmp::max(textwrap::termwidth(), 20);
enum TableMode {
Light,
Normal,
}
let mut table = Table::new();
let table_mode = crate::data::config::config(Tag::unknown());
let table_mode = if let Some(s) = table_mode?.get("table_mode") {
match s.as_string() {
Ok(typ) if typ == "light" => TableMode::Light,
_ => TableMode::Normal,
}
} else {
TableMode::Normal
};
match table_mode {
TableMode::Light => {
table.set_format(
FormatBuilder::new()
.separator(
LinePosition::Title,
LineSeparator::new('─', '─', ' ', ' '),
)
.separator(
LinePosition::Bottom,
LineSeparator::new(' ', ' ', ' ', ' '),
)
.padding(1, 1)
.build(),
);
}
_ => {
table.set_format(
FormatBuilder::new()
.column_separator('│')
.separator(
LinePosition::Top,
LineSeparator::new('─', '┬', ' ', ' '),
)
.separator(
LinePosition::Title,
LineSeparator::new('─', '┼', ' ', ' '),
)
.separator(
LinePosition::Bottom,
LineSeparator::new('─', '┴', ' ', ' '),
)
.padding(1, 1)
.build(),
);
}
}
let mut max_key_len = 0;
for (key, _) in row.entries.iter() {
max_key_len = std::cmp::max(max_key_len, key.chars().count());
}
if max_key_len > (termwidth / 2 - 1) {
max_key_len = termwidth / 2 - 1;
}
let max_val_len = termwidth - max_key_len - 5;
let mut entries = vec![];
for (key, value) in row.entries.iter() {
table.add_row(Row::new(vec![
Cell::new(&fill(&key, max_key_len))
.with_style(Attr::ForegroundColor(color::GREEN))
.with_style(Attr::Bold),
Cell::new(&fill(
&format_leaf(value).plain_string(100_000),
max_val_len,
)),
]));
entries.push(vec![
nu_table::StyledString::new(
key.to_string(),
nu_table::TextStyle {
alignment: nu_table::Alignment::Left,
color: Some(ansi_term::Color::Green),
is_bold: true,
},
),
nu_table::StyledString::new(
format_leaf(value).plain_string(100_000),
nu_table::TextStyle::basic(),
),
]);
}
table.printstd();
let table =
nu_table::Table::new(vec![], entries, nu_table::Theme::compact());
// table.print_term(&mut *context.host.lock().out_terminal().ok_or_else(|| ShellError::untagged_runtime_error("Could not open terminal for output"))?)
// .map_err(|_| ShellError::untagged_runtime_error("Internal error: could not print to terminal (for unix systems check to make sure TERM is set)"))?;
nu_table::draw_table(&table, textwrap::termwidth());
}
Value {

View File

@ -24,6 +24,12 @@ impl WholeStreamCommand for Cal {
"Display a year-long calendar for the specified year",
None,
)
.named(
"week-start",
SyntaxShape::String,
"Display the calendar with the specified day as the first day of the week",
None,
)
.switch(
"month-names",
"Display the month names instead of integers",
@ -55,6 +61,11 @@ impl WholeStreamCommand for Cal {
example: "cal --full-year 2012",
result: None,
},
Example {
description: "This month's calendar with the week starting on monday",
example: "cal --week-start monday",
result: None,
},
]
}
}
@ -112,90 +123,48 @@ fn get_invalid_year_shell_error(year_tag: &Tag) -> ShellError {
}
struct MonthHelper {
day_number_month_starts_on: u32,
number_of_days_in_month: u32,
selected_year: i32,
selected_month: u32,
day_number_of_week_month_starts_on: u32,
number_of_days_in_month: u32,
quarter_number: u32,
month_name: String,
}
impl MonthHelper {
pub fn new(selected_year: i32, selected_month: u32) -> Result<MonthHelper, ()> {
let mut month_helper = MonthHelper {
day_number_month_starts_on: 0,
number_of_days_in_month: 0,
let naive_date = NaiveDate::from_ymd_opt(selected_year, selected_month, 1).ok_or(())?;
let number_of_days_in_month =
MonthHelper::calculate_number_of_days_in_month(selected_year, selected_month)?;
Ok(MonthHelper {
selected_year,
selected_month,
};
let chosen_date_result_one = month_helper.update_day_number_month_starts_on();
let chosen_date_result_two = month_helper.update_number_of_days_in_month();
if chosen_date_result_one.is_ok() && chosen_date_result_two.is_ok() {
return Ok(month_helper);
}
Err(())
day_number_of_week_month_starts_on: naive_date.weekday().num_days_from_sunday(),
number_of_days_in_month,
quarter_number: ((selected_month - 1) / 3) + 1,
month_name: naive_date.format("%B").to_string().to_ascii_lowercase(),
})
}
pub fn get_month_name(&self) -> String {
let month_name = match self.selected_month {
1 => "january",
2 => "february",
3 => "march",
4 => "april",
5 => "may",
6 => "june",
7 => "july",
8 => "august",
9 => "september",
10 => "october",
11 => "november",
_ => "december",
};
month_name.to_string()
}
fn update_day_number_month_starts_on(&mut self) -> Result<(), ()> {
let naive_date_result =
MonthHelper::get_naive_date(self.selected_year, self.selected_month);
match naive_date_result {
Ok(naive_date) => {
self.day_number_month_starts_on = naive_date.weekday().num_days_from_sunday();
Ok(())
}
_ => Err(()),
}
}
fn update_number_of_days_in_month(&mut self) -> Result<(), ()> {
fn calculate_number_of_days_in_month(
mut selected_year: i32,
mut selected_month: u32,
) -> Result<u32, ()> {
// Chrono does not provide a method to output the amount of days in a month
// This is a workaround taken from the example code from the Chrono docs here:
// https://docs.rs/chrono/0.3.0/chrono/naive/date/struct.NaiveDate.html#example-30
let (adjusted_year, adjusted_month) = if self.selected_month == 12 {
(self.selected_year + 1, 1)
if selected_month == 12 {
selected_year += 1;
selected_month = 1;
} else {
(self.selected_year, self.selected_month + 1)
selected_month += 1;
};
let naive_date_result = MonthHelper::get_naive_date(adjusted_year, adjusted_month);
let next_month_naive_date =
NaiveDate::from_ymd_opt(selected_year, selected_month, 1).ok_or(())?;
match naive_date_result {
Ok(naive_date) => {
self.number_of_days_in_month = naive_date.pred().day();
Ok(())
}
_ => Err(()),
}
}
fn get_naive_date(selected_year: i32, selected_month: u32) -> Result<NaiveDate, ()> {
if let Some(naive_date) = NaiveDate::from_ymd_opt(selected_year, selected_month, 1) {
return Ok(naive_date);
}
Err(())
Ok(next_month_naive_date.pred().day())
}
}
@ -268,10 +237,7 @@ fn add_month_to_table(
},
};
let day_limit = month_helper.number_of_days_in_month + month_helper.day_number_month_starts_on;
let mut day_count: u32 = 1;
let days_of_the_week = [
let mut days_of_the_week = [
"sunday",
"monday",
"tuesday",
@ -281,12 +247,43 @@ fn add_month_to_table(
"saturday",
];
let mut week_start_day = days_of_the_week[0].to_string();
if let Some(week_start_value) = args.get("week-start") {
if let Ok(day) = week_start_value.as_string() {
if days_of_the_week.contains(&day.as_str()) {
week_start_day = day;
} else {
return Err(ShellError::labeled_error(
"The specified week start day is invalid",
"invalid week start day",
week_start_value.tag(),
));
}
}
}
let week_start_day_offset = days_of_the_week.len()
- days_of_the_week
.iter()
.position(|day| *day == week_start_day)
.unwrap_or(0);
days_of_the_week.rotate_right(week_start_day_offset);
let mut total_start_offset: u32 =
month_helper.day_number_of_week_month_starts_on + week_start_day_offset as u32;
total_start_offset %= days_of_the_week.len() as u32;
let mut day_number: u32 = 1;
let day_limit: u32 = total_start_offset + month_helper.number_of_days_in_month;
let should_show_year_column = args.has("year");
let should_show_month_column = args.has("month");
let should_show_quarter_column = args.has("quarter");
let should_show_month_column = args.has("month");
let should_show_month_names = args.has("month-names");
while day_count <= day_limit {
while day_number <= day_limit {
let mut indexmap = IndexMap::new();
if should_show_year_column {
@ -299,13 +296,13 @@ fn add_month_to_table(
if should_show_quarter_column {
indexmap.insert(
"quarter".to_string(),
UntaggedValue::int(((month_helper.selected_month - 1) / 3) + 1).into_value(tag),
UntaggedValue::int(month_helper.quarter_number).into_value(tag),
);
}
if should_show_month_column {
let month_value = if should_show_month_names {
UntaggedValue::string(month_helper.get_month_name()).into_value(tag)
UntaggedValue::string(month_helper.month_name.clone()).into_value(tag)
} else {
UntaggedValue::int(month_helper.selected_month).into_value(tag)
};
@ -315,17 +312,17 @@ fn add_month_to_table(
for day in &days_of_the_week {
let should_add_day_number_to_table =
(day_count <= day_limit) && (day_count > month_helper.day_number_month_starts_on);
(day_number > total_start_offset) && (day_number <= day_limit);
let mut value = UntaggedValue::nothing().into_value(tag);
if should_add_day_number_to_table {
let day_count_with_offset = day_count - month_helper.day_number_month_starts_on;
let adjusted_day_number = day_number - total_start_offset;
value = UntaggedValue::int(day_count_with_offset).into_value(tag);
value = UntaggedValue::int(adjusted_day_number).into_value(tag);
if let Some(current_day) = current_day_option {
if current_day == day_count_with_offset {
if current_day == adjusted_day_number {
// TODO: Update the value here with a color when color support is added
// This colors the current day
}
@ -334,7 +331,7 @@ fn add_month_to_table(
indexmap.insert((*day).to_string(), value);
day_count += 1;
day_number += 1;
}
calendar_vec_deque

View File

@ -56,7 +56,7 @@ impl WholeStreamCommand for Each {
},
Example {
description: "Echo the sum of each row",
example: "echo [[1 2] [3 4]] | each { echo $it | sum }",
example: "echo [[1 2] [3 4]] | each { echo $it | math sum }",
result: Some(vec![
UntaggedValue::int(3).into(),
UntaggedValue::int(7).into(),

View File

@ -0,0 +1,105 @@
use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue};
use nu_source::Tagged;
pub struct Every;
#[derive(Deserialize)]
pub struct EveryArgs {
stride: Tagged<u64>,
skip: Tagged<bool>,
}
#[async_trait]
impl WholeStreamCommand for Every {
fn name(&self) -> &str {
"every"
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.required(
"stride",
SyntaxShape::Int,
"how many rows to skip between (and including) each row returned",
)
.switch(
"skip",
"skip the rows that would be returned, instead of selecting them",
Some('s'),
)
}
fn usage(&self) -> &str {
"Show (or skip) every n-th row, starting from the first one."
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
every(args, registry).await
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Get every second row",
example: "echo [1 2 3 4 5] | every 2",
result: Some(vec![
UntaggedValue::int(1).into(),
UntaggedValue::int(3).into(),
UntaggedValue::int(5).into(),
]),
},
Example {
description: "Skip every second row",
example: "echo [1 2 3 4 5] | every 2 --skip",
result: Some(vec![
UntaggedValue::int(2).into(),
UntaggedValue::int(4).into(),
]),
},
]
}
}
async fn every(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let (EveryArgs { stride, skip }, input) = args.process(&registry).await?;
let v: Vec<_> = input.into_vec().await;
let stride_desired = if stride.item < 1 { 1 } else { stride.item } as usize;
let mut values_vec_deque = VecDeque::new();
for (i, x) in v.iter().enumerate() {
let should_include = if skip.item {
i % stride_desired != 0
} else {
i % stride_desired == 0
};
if should_include {
values_vec_deque.push_back(ReturnSuccess::value(x.clone()));
}
}
Ok(futures::stream::iter(values_vec_deque).to_output_stream())
}
#[cfg(test)]
mod tests {
use super::Every;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Every {})
}
}

View File

@ -59,27 +59,19 @@ fn convert_yaml_value_to_nu_value(
) -> Result<Value, ShellError> {
let tag = tag.into();
let err_not_compatible_number = ShellError::labeled_error(
"Expected a compatible number",
"expected a compatible number",
&tag,
);
Ok(match v {
serde_yaml::Value::Bool(b) => UntaggedValue::boolean(*b).into_value(tag),
serde_yaml::Value::Number(n) if n.is_i64() => {
UntaggedValue::int(n.as_i64().ok_or_else(|| {
ShellError::labeled_error(
"Expected a compatible number",
"expected a compatible number",
&tag,
)
})?)
.into_value(tag)
UntaggedValue::int(n.as_i64().ok_or_else(|| err_not_compatible_number)?).into_value(tag)
}
serde_yaml::Value::Number(n) if n.is_f64() => {
UntaggedValue::decimal(n.as_f64().ok_or_else(|| {
ShellError::labeled_error(
"Expected a compatible number",
"expected a compatible number",
&tag,
)
})?)
.into_value(tag)
UntaggedValue::decimal(n.as_f64().ok_or_else(|| err_not_compatible_number)?)
.into_value(tag)
}
serde_yaml::Value::String(s) => UntaggedValue::string(s).into_value(tag),
serde_yaml::Value::Sequence(a) => {
@ -93,11 +85,39 @@ fn convert_yaml_value_to_nu_value(
let mut collected = TaggedDictBuilder::new(&tag);
for (k, v) in t.iter() {
match k {
serde_yaml::Value::String(k) => {
// A ShellError that we re-use multiple times in the Mapping scenario
let err_unexpected_map = ShellError::labeled_error(
format!("Unexpected YAML:\nKey: {:?}\nValue: {:?}", k, v),
"unexpected",
tag.clone(),
);
match (k, v) {
(serde_yaml::Value::String(k), _) => {
collected.insert_value(k.clone(), convert_yaml_value_to_nu_value(v, &tag)?);
}
_ => unimplemented!("Unknown key type"),
// Hard-code fix for cases where "v" is a string without quotations with double curly braces
// e.g. k = value
// value: {{ something }}
// Strangely, serde_yaml returns
// "value" -> Mapping(Mapping { map: {Mapping(Mapping { map: {String("something"): Null} }): Null} })
(serde_yaml::Value::Mapping(m), serde_yaml::Value::Null) => {
return m
.iter()
.take(1)
.collect_vec()
.first()
.and_then(|e| match e {
(serde_yaml::Value::String(s), serde_yaml::Value::Null) => Some(
UntaggedValue::string("{{ ".to_owned() + &s + " }}")
.into_value(tag),
),
_ => None,
})
.ok_or(err_unexpected_map);
}
(_, _) => {
return Err(err_unexpected_map);
}
}
}
@ -151,7 +171,9 @@ async fn from_yaml(
#[cfg(test)]
mod tests {
use super::FromYAML;
use super::*;
use nu_plugin::row;
use nu_plugin::test_helpers::value::string;
#[test]
fn examples_work_as_expected() {
@ -159,4 +181,38 @@ mod tests {
test_examples(FromYAML {})
}
#[test]
fn test_problematic_yaml() {
struct TestCase {
description: &'static str,
input: &'static str,
expected: Result<Value, ShellError>,
}
let tt: Vec<TestCase> = vec![
TestCase {
description: "Double Curly Braces With Quotes",
input: r#"value: "{{ something }}""#,
expected: Ok(row!["value".to_owned() => string("{{ something }}")]),
},
TestCase {
description: "Double Curly Braces Without Quotes",
input: r#"value: {{ something }}"#,
expected: Ok(row!["value".to_owned() => string("{{ something }}")]),
},
];
for tc in tt.into_iter() {
let actual = from_yaml_string_to_value(tc.input.to_owned(), Tag::default());
if actual.is_err() {
assert!(
tc.expected.is_err(),
"actual is Err for test:\nTest Description {}\nErr: {:?}",
tc.description,
actual
);
} else {
assert_eq!(actual, tc.expected, "{}", tc.description);
}
}
}
}

View File

@ -4,6 +4,7 @@ use indexmap::indexmap;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
use nu_source::Tagged;
use nu_value_ext::as_string;
pub struct GroupBy;
@ -71,6 +72,10 @@ impl WholeStreamCommand for GroupBy {
}
}
enum Grouper {
ByColumn(Option<Tagged<String>>),
}
pub async fn group_by(
args: CommandArgs,
registry: &CommandRegistry,
@ -81,30 +86,84 @@ pub async fn group_by(
let values: Vec<Value> = input.collect().await;
if values.is_empty() {
Err(ShellError::labeled_error(
return Err(ShellError::labeled_error(
"Expected table from pipeline",
"requires a table input",
name,
))
));
}
let values = UntaggedValue::table(&values).into_value(&name);
match group(&column_name, &values, name) {
Ok(grouped) => Ok(OutputStream::one(ReturnSuccess::value(grouped))),
Err(reason) => Err(reason),
}
}
pub fn suggestions(tried: Tagged<&str>, for_value: &Value) -> ShellError {
let possibilities = for_value.data_descriptors();
let mut possible_matches: Vec<_> = possibilities
.iter()
.map(|x| (natural::distance::levenshtein_distance(x, &tried), x))
.collect();
possible_matches.sort();
if !possible_matches.is_empty() {
ShellError::labeled_error(
"Unknown column",
format!("did you mean '{}'?", possible_matches[0].1),
tried.tag(),
)
} else {
match crate::utils::data::group(column_name, &values, None, &name) {
Ok(grouped) => Ok(OutputStream::one(ReturnSuccess::value(grouped))),
Err(err) => Err(err),
}
ShellError::labeled_error(
"Unknown column",
"row does not contain this column",
tried.tag(),
)
}
}
pub fn group(
column_name: &Tagged<String>,
values: Vec<Value>,
column_name: &Option<Tagged<String>>,
values: &Value,
tag: impl Into<Tag>,
) -> Result<Value, ShellError> {
crate::utils::data::group(Some(column_name.clone()), &values, None, tag)
let name = tag.into();
let grouper = if let Some(column_name) = column_name {
Grouper::ByColumn(Some(column_name.clone()))
} else {
Grouper::ByColumn(None)
};
match grouper {
Grouper::ByColumn(Some(column_name)) => {
let block = Box::new(move |row: &Value| {
match row.get_data_by_key(column_name.borrow_spanned()) {
Some(group_key) => Ok(as_string(&group_key)?),
None => Err(suggestions(column_name.borrow_tagged(), &row)),
}
});
crate::utils::data::group(&values, &Some(block), &name)
}
Grouper::ByColumn(None) => {
let block = Box::new(move |row: &Value| match as_string(row) {
Ok(group_key) => Ok(group_key),
Err(reason) => Err(reason),
});
crate::utils::data::group(&values, &Some(block), &name)
}
}
}
#[cfg(test)]
mod tests {
use crate::commands::group_by::group;
use super::group;
use indexmap::IndexMap;
use nu_errors::ShellError;
use nu_protocol::{UntaggedValue, Value};
@ -122,7 +181,7 @@ mod tests {
UntaggedValue::table(list).into_untagged_value()
}
fn nu_releases_commiters() -> Vec<Value> {
fn nu_releases_committers() -> Vec<Value> {
vec![
row(
indexmap! {"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => string("August 23-2019")},
@ -156,10 +215,11 @@ mod tests {
#[test]
fn groups_table_by_date_column() -> Result<(), ShellError> {
let for_key = String::from("date").tagged_unknown();
let for_key = Some(String::from("date").tagged_unknown());
let sample = table(&nu_releases_committers());
assert_eq!(
group(&for_key, nu_releases_commiters(), Tag::unknown())?,
group(&for_key, &sample, Tag::unknown())?,
row(indexmap! {
"August 23-2019".into() => table(&[
row(indexmap!{"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => string("August 23-2019")}),
@ -184,10 +244,11 @@ mod tests {
#[test]
fn groups_table_by_country_column() -> Result<(), ShellError> {
let for_key = String::from("country").tagged_unknown();
let for_key = Some(String::from("country").tagged_unknown());
let sample = table(&nu_releases_committers());
assert_eq!(
group(&for_key, nu_releases_commiters(), Tag::unknown())?,
group(&for_key, &sample, Tag::unknown())?,
row(indexmap! {
"EC".into() => table(&[
row(indexmap!{"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => string("August 23-2019")}),

View File

@ -1,7 +1,7 @@
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, Value};
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
use nu_source::Tagged;
pub struct GroupByDate;
@ -55,7 +55,11 @@ impl WholeStreamCommand for GroupByDate {
}
enum Grouper {
ByDate(Option<String>),
ByDate(Option<Tagged<String>>),
}
enum GroupByColumn {
Name(Option<Tagged<String>>),
}
pub async fn group_by_date(
@ -80,31 +84,63 @@ pub async fn group_by_date(
name,
))
} else {
let grouper = if let Some(Tagged { item: fmt, tag: _ }) = format {
Grouper::ByDate(Some(fmt))
let values = UntaggedValue::table(&values).into_value(&name);
let grouper_column = if let Some(column_name) = column_name {
GroupByColumn::Name(Some(column_name))
} else {
GroupByColumn::Name(None)
};
let grouper_date = if let Some(date_format) = format {
Grouper::ByDate(Some(date_format))
} else {
Grouper::ByDate(None)
};
match grouper {
Grouper::ByDate(None) => {
match crate::utils::data::group(
column_name,
&values,
Some(Box::new(|row: &Value| row.format("%Y-%b-%d"))),
&name,
) {
match (grouper_date, grouper_column) {
(Grouper::ByDate(None), GroupByColumn::Name(None)) => {
let block = Box::new(move |row: &Value| row.format("%Y-%b-%d"));
match crate::utils::data::group(&values, &Some(block), &name) {
Ok(grouped) => Ok(OutputStream::one(ReturnSuccess::value(grouped))),
Err(err) => Err(err),
}
}
Grouper::ByDate(Some(fmt)) => {
match crate::utils::data::group(
column_name,
&values,
Some(Box::new(move |row: &Value| row.format(&fmt))),
&name,
) {
(Grouper::ByDate(None), GroupByColumn::Name(Some(column_name))) => {
let block = Box::new(move |row: &Value| {
let group_key = match row.get_data_by_key(column_name.borrow_spanned()) {
Some(group_key) => Ok(group_key),
None => Err(suggestions(column_name.borrow_tagged(), &row)),
};
group_key?.format("%Y-%b-%d")
});
match crate::utils::data::group(&values, &Some(block), &name) {
Ok(grouped) => Ok(OutputStream::one(ReturnSuccess::value(grouped))),
Err(err) => Err(err),
}
}
(Grouper::ByDate(Some(fmt)), GroupByColumn::Name(None)) => {
let block = Box::new(move |row: &Value| row.format(&fmt));
match crate::utils::data::group(&values, &Some(block), &name) {
Ok(grouped) => Ok(OutputStream::one(ReturnSuccess::value(grouped))),
Err(err) => Err(err),
}
}
(Grouper::ByDate(Some(fmt)), GroupByColumn::Name(Some(column_name))) => {
let block = Box::new(move |row: &Value| {
let group_key = match row.get_data_by_key(column_name.borrow_spanned()) {
Some(group_key) => Ok(group_key),
None => Err(suggestions(column_name.borrow_tagged(), &row)),
};
group_key?.format(&fmt)
});
match crate::utils::data::group(&values, &Some(block), &name) {
Ok(grouped) => Ok(OutputStream::one(ReturnSuccess::value(grouped))),
Err(err) => Err(err),
}
@ -113,6 +149,31 @@ pub async fn group_by_date(
}
}
pub fn suggestions(tried: Tagged<&str>, for_value: &Value) -> ShellError {
let possibilities = for_value.data_descriptors();
let mut possible_matches: Vec<_> = possibilities
.iter()
.map(|x| (natural::distance::levenshtein_distance(x, &tried), x))
.collect();
possible_matches.sort();
if !possible_matches.is_empty() {
ShellError::labeled_error(
"Unknown column",
format!("did you mean '{}'?", possible_matches[0].1),
tried.tag(),
)
} else {
ShellError::labeled_error(
"Unknown column",
"row does not contain this column",
tried.tag(),
)
}
}
#[cfg(test)]
mod tests {
use super::GroupByDate;

View File

@ -76,14 +76,14 @@ pub async fn histogram(
) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let name = args.call_info.name_tag.clone();
let (HistogramArgs { column_name, rest }, input) = args.process(&registry).await?;
let values: Vec<Value> = input.collect().await;
let values = UntaggedValue::table(&values).into_value(&name);
let Tagged { item: group_by, .. } = column_name.clone();
let groups = group(&column_name, values, &name)?;
let group_labels = columns_sorted(Some(group_by.clone()), &groups, &name);
let sorted = t_sort(Some(group_by), None, &groups, &name)?;
let groups = group(&Some(column_name.clone()), &values, &name)?;
let group_labels = columns_sorted(Some(column_name.clone()), &groups, &name);
let sorted = t_sort(Some(column_name.clone()), None, &groups, &name)?;
let evaled = evaluate(&sorted, None, &name)?;
let reduced = reduce(&evaled, None, &name)?;
let maxima = map_max(&reduced, None, &name)?;

View File

@ -14,15 +14,15 @@ pub struct SubCommand;
#[async_trait]
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"math average"
"math avg"
}
fn signature(&self) -> Signature {
Signature::build("math average")
Signature::build("math avg")
}
fn usage(&self) -> &str {
"Gets the average of a list of numbers"
"Finds the average of a list of numbers or tables"
}
async fn run(
@ -49,16 +49,22 @@ impl WholeStreamCommand for SubCommand {
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Get the average of a list of numbers",
example: "echo [-50 100.0 25] | math average",
example: "echo [-50 100.0 25] | math avg",
result: Some(vec![UntaggedValue::decimal(25).into()]),
}]
}
}
pub fn average(values: &[Value], name: &Tag) -> Result<Value, ShellError> {
let sum = reducer_for(Reduce::Sum);
let sum = reducer_for(Reduce::Summation);
let number = BigDecimal::from_usize(values.len()).expect("expected a usize-sized bigdecimal");
let number = BigDecimal::from_usize(values.len()).ok_or_else(|| {
ShellError::labeled_error(
"could not convert to big decimal",
"could not convert to big decimal",
&name.span,
)
})?;
let total_rows = UntaggedValue::decimal(number);
let total = sum(Value::zero(), values.to_vec())?;

View File

@ -35,7 +35,8 @@ impl WholeStreamCommand for Command {
mod tests {
use super::*;
use crate::commands::math::{
average::average, max::maximum, min::minimum, utils::MathFunction,
avg::average, max::maximum, median::median, min::minimum, sum::summation,
utils::MathFunction,
};
use nu_plugin::test_helpers::value::{decimal, int};
use nu_protocol::Value;
@ -67,31 +68,61 @@ mod tests {
description: "Single value",
values: vec![int(10)],
expected_err: None,
expected_res: vec![Ok(decimal(10)), Ok(int(10)), Ok(int(10))],
expected_res: vec![
Ok(decimal(10)),
Ok(int(10)),
Ok(int(10)),
Ok(int(10)),
Ok(int(10)),
],
},
TestCase {
description: "Multiple Values",
values: vec![int(10), int(30), int(20)],
values: vec![int(10), int(20), int(30)],
expected_err: None,
expected_res: vec![Ok(decimal(20)), Ok(int(10)), Ok(int(30))],
expected_res: vec![
Ok(decimal(20)),
Ok(int(10)),
Ok(int(30)),
Ok(int(20)),
Ok(int(60)),
],
},
TestCase {
description: "Mixed Values",
values: vec![int(10), decimal(26.5), decimal(26.5)],
expected_err: None,
expected_res: vec![Ok(decimal(21)), Ok(int(10)), Ok(decimal(26.5))],
expected_res: vec![
Ok(decimal(21)),
Ok(int(10)),
Ok(decimal(26.5)),
Ok(decimal(26.5)),
Ok(decimal(63)),
],
},
TestCase {
description: "Negative Values",
values: vec![int(10), int(-11), int(-14)],
values: vec![int(-14), int(-11), int(10)],
expected_err: None,
expected_res: vec![Ok(decimal(-5)), Ok(int(-14)), Ok(int(10))],
expected_res: vec![
Ok(decimal(-5)),
Ok(int(-14)),
Ok(int(10)),
Ok(int(-11)),
Ok(int(-15)),
],
},
TestCase {
description: "Mixed Negative Values",
values: vec![int(10), decimal(-11.5), decimal(-13.5)],
values: vec![decimal(-13.5), decimal(-11.5), int(10)],
expected_err: None,
expected_res: vec![Ok(decimal(-5)), Ok(decimal(-13.5)), Ok(int(10))],
expected_res: vec![
Ok(decimal(-5)),
Ok(decimal(-13.5)),
Ok(int(10)),
Ok(decimal(-11.5)),
Ok(decimal(-15)),
],
},
// TODO-Uncomment once I figure out how to structure tables
// TestCase {
@ -116,7 +147,8 @@ mod tests {
for tc in tt.iter() {
let tc: &TestCase = tc; // Just for type annotations
let math_functions: Vec<MathFunction> = vec![average, minimum, maximum];
let math_functions: Vec<MathFunction> =
vec![average, minimum, maximum, median, summation];
let results = math_functions
.iter()
.map(|mf| mf(&tc.values, &test_tag))

View File

@ -18,7 +18,7 @@ impl WholeStreamCommand for SubCommand {
}
fn usage(&self) -> &str {
"Get the maximum of a list of numbers or tables"
"Finds the maximum within a list of numbers or tables"
}
async fn run(

View File

@ -0,0 +1,193 @@
use crate::commands::math::utils::calculate;
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use crate::utils::data_processing::{reducer_for, Reduce};
use bigdecimal::{FromPrimitive, Zero};
use nu_errors::ShellError;
use nu_protocol::{
hir::{convert_number_to_u64, Number, Operator},
Primitive, Signature, UntaggedValue, Value,
};
pub struct SubCommand;
#[async_trait]
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"math median"
}
fn signature(&self) -> Signature {
Signature::build("math median")
}
fn usage(&self) -> &str {
"Gets the median of a list of numbers"
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
calculate(
RunnableContext {
input: args.input,
registry: registry.clone(),
shell_manager: args.shell_manager,
host: args.host,
ctrl_c: args.ctrl_c,
current_errors: args.current_errors,
name: args.call_info.name_tag,
raw_input: args.raw_input,
},
median,
)
.await
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Get the median of a list of numbers",
example: "echo [3 8 9 12 12 15] | math median",
result: Some(vec![UntaggedValue::decimal(10.5).into()]),
}]
}
}
enum Pick {
MedianAverage,
Median,
}
pub fn median(values: &[Value], name: &Tag) -> Result<Value, ShellError> {
let take = if values.len() % 2 == 0 {
Pick::MedianAverage
} else {
Pick::Median
};
let mut sorted = vec![];
for item in values {
sorted.push(item.clone());
}
crate::commands::sort_by::sort(&mut sorted, &[], name)?;
match take {
Pick::Median => {
let idx = (values.len() as f64 / 2.0).floor() as usize;
let out = sorted.get(idx).ok_or_else(|| {
ShellError::labeled_error(
"could not extract value",
"could not extract value",
&name.span,
)
})?;
Ok(out.clone())
}
Pick::MedianAverage => {
let idx_end = (values.len() / 2) as usize;
let idx_start = idx_end - 1;
let left = sorted
.get(idx_start)
.ok_or_else(|| {
ShellError::labeled_error(
"could not extract value",
"could not extract value",
&name.span,
)
})?
.clone();
let right = sorted
.get(idx_end)
.ok_or_else(|| {
ShellError::labeled_error(
"could not extract value",
"could not extract value",
&name.span,
)
})?
.clone();
compute_average(&[left, right], name)
}
}
}
fn compute_average(values: &[Value], name: impl Into<Tag>) -> Result<Value, ShellError> {
let name = name.into();
let sum = reducer_for(Reduce::Summation);
let number = BigDecimal::from_usize(2).ok_or_else(|| {
ShellError::labeled_error(
"could not convert to big decimal",
"could not convert to big decimal",
&name,
)
})?;
let total_rows = UntaggedValue::decimal(number);
let total = sum(Value::zero(), values.to_vec())?;
match total {
Value {
value: UntaggedValue::Primitive(Primitive::Bytes(num)),
..
} => {
let left = UntaggedValue::from(Primitive::Int(num.into()));
let result = crate::data::value::compute_values(Operator::Divide, &left, &total_rows);
match result {
Ok(UntaggedValue::Primitive(Primitive::Decimal(result))) => {
let number = Number::Decimal(result);
let number = convert_number_to_u64(&number);
Ok(UntaggedValue::bytes(number).into_value(name))
}
Ok(_) => Err(ShellError::labeled_error(
"could not calculate median of non-numeric or unrelated types",
"source",
name,
)),
Err((left_type, right_type)) => Err(ShellError::coerce_error(
left_type.spanned(name.span),
right_type.spanned(name.span),
)),
}
}
Value {
value: UntaggedValue::Primitive(other),
..
} => {
let left = UntaggedValue::from(other);
let result = crate::data::value::compute_values(Operator::Divide, &left, &total_rows);
match result {
Ok(value) => Ok(value.into_value(name)),
Err((left_type, right_type)) => Err(ShellError::coerce_error(
left_type.spanned(name.span),
right_type.spanned(name.span),
)),
}
}
_ => Err(ShellError::labeled_error(
"could not calculate median of non-numeric or unrelated types",
"source",
name,
)),
}
}
#[cfg(test)]
mod tests {
use super::SubCommand;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(SubCommand {})
}
}

View File

@ -1,10 +1,14 @@
pub mod average;
pub mod avg;
pub mod command;
pub mod max;
pub mod median;
pub mod min;
pub mod sum;
pub mod utils;
pub use average::SubCommand as Average;
pub use avg::SubCommand as MathAverage;
pub use command::Command as Math;
pub use max::SubCommand as Maximum;
pub use min::SubCommand as Minimum;
pub use max::SubCommand as MathMaximum;
pub use median::SubCommand as MathMedian;
pub use min::SubCommand as MathMinimum;
pub use sum::SubCommand as MathSummation;

View File

@ -1,26 +1,25 @@
use crate::commands::math::utils::calculate;
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use crate::utils::data_processing::{reducer_for, Reduce};
use nu_errors::ShellError;
use nu_protocol::{Dictionary, ReturnSuccess, Signature, UntaggedValue, Value};
use nu_protocol::{Dictionary, Signature, UntaggedValue, Value};
use num_traits::identities::Zero;
use indexmap::map::IndexMap;
pub struct Sum;
pub struct SubCommand;
#[async_trait]
impl WholeStreamCommand for Sum {
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"sum"
"math sum"
}
fn signature(&self) -> Signature {
Signature::build("sum")
Signature::build("math sum")
}
fn usage(&self) -> &str {
"Sums the values."
"Finds the sum of a list of numbers or tables"
}
async fn run(
@ -28,16 +27,19 @@ impl WholeStreamCommand for Sum {
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
sum(RunnableContext {
input: args.input,
registry: registry.clone(),
shell_manager: args.shell_manager,
host: args.host,
ctrl_c: args.ctrl_c,
current_errors: args.current_errors,
name: args.call_info.name_tag,
raw_input: args.raw_input,
})
calculate(
RunnableContext {
input: args.input,
registry: registry.clone(),
shell_manager: args.shell_manager,
host: args.host,
ctrl_c: args.ctrl_c,
current_errors: args.current_errors,
name: args.call_info.name_tag,
raw_input: args.raw_input,
},
summation,
)
.await
}
@ -45,31 +47,28 @@ impl WholeStreamCommand for Sum {
vec![
Example {
description: "Sum a list of numbers",
example: "echo [1 2 3] | sum",
example: "echo [1 2 3] | math sum",
result: Some(vec![UntaggedValue::int(6).into()]),
},
Example {
description: "Get the disk usage for the current directory",
example: "ls --all --du | get size | sum",
example: "ls --all --du | get size | math sum",
result: None,
},
]
}
}
async fn sum(
RunnableContext { mut input, .. }: RunnableContext,
) -> Result<OutputStream, ShellError> {
let values: Vec<Value> = input.drain_vec().await;
let action = reducer_for(Reduce::Sum);
pub fn summation(values: &[Value], name: &Tag) -> Result<Value, ShellError> {
let sum = reducer_for(Reduce::Summation);
if values.iter().all(|v| v.is_primitive()) {
let total = action(Value::zero(), values)?;
Ok(OutputStream::one(ReturnSuccess::value(total)))
Ok(sum(Value::zero(), values.to_vec())?)
} else {
let mut column_values = IndexMap::new();
for value in values {
if let UntaggedValue::Row(row_dict) = value.value {
if let UntaggedValue::Row(row_dict) = value.value.clone() {
for (key, value) in row_dict.entries.iter() {
column_values
.entry(key.clone())
@ -80,32 +79,28 @@ async fn sum(
}
let mut column_totals = IndexMap::new();
for (col_name, col_vals) in column_values {
let sum = action(Value::zero(), col_vals);
match sum {
Ok(value) => {
column_totals.insert(col_name, value);
}
Err(err) => return Err(err),
};
let sum = sum(Value::zero(), col_vals)?;
column_totals.insert(col_name, sum);
}
Ok(OutputStream::one(ReturnSuccess::value(
UntaggedValue::Row(Dictionary {
entries: column_totals,
})
.into_untagged_value(),
)))
Ok(UntaggedValue::Row(Dictionary {
entries: column_totals,
})
.into_value(name))
}
}
#[cfg(test)]
mod tests {
use super::Sum;
use super::SubCommand;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Sum {})
test_examples(SubCommand {})
}
}

View File

@ -305,132 +305,6 @@ pub async fn fetch(
span,
))
}
/*
cwd.push(Path::new(location));
if let Ok(cwd) = dunce::canonicalize(cwd) {
match std::fs::read(&cwd) {
Ok(bytes) => match std::str::from_utf8(&bytes) {
Ok(s) => Ok((
cwd.extension()
.map(|name| name.to_string_lossy().to_string()),
UntaggedValue::string(s),
Tag {
span,
anchor: Some(AnchorLocation::File(cwd.to_string_lossy().to_string())),
},
)),
Err(_) => {
//Non utf8 data.
match (bytes.get(0), bytes.get(1)) {
(Some(x), Some(y)) if *x == 0xff && *y == 0xfe => {
// Possibly UTF-16 little endian
let utf16 = read_le_u16(&bytes[2..]);
if let Some(utf16) = utf16 {
match std::string::String::from_utf16(&utf16) {
Ok(s) => Ok((
cwd.extension()
.map(|name| name.to_string_lossy().to_string()),
UntaggedValue::string(s),
Tag {
span,
anchor: Some(AnchorLocation::File(
cwd.to_string_lossy().to_string(),
)),
},
)),
Err(_) => Ok((
None,
UntaggedValue::binary(bytes),
Tag {
span,
anchor: Some(AnchorLocation::File(
cwd.to_string_lossy().to_string(),
)),
},
)),
}
} else {
Ok((
None,
UntaggedValue::binary(bytes),
Tag {
span,
anchor: Some(AnchorLocation::File(
cwd.to_string_lossy().to_string(),
)),
},
))
}
}
(Some(x), Some(y)) if *x == 0xfe && *y == 0xff => {
// Possibly UTF-16 big endian
let utf16 = read_be_u16(&bytes[2..]);
if let Some(utf16) = utf16 {
match std::string::String::from_utf16(&utf16) {
Ok(s) => Ok((
cwd.extension()
.map(|name| name.to_string_lossy().to_string()),
UntaggedValue::string(s),
Tag {
span,
anchor: Some(AnchorLocation::File(
cwd.to_string_lossy().to_string(),
)),
},
)),
Err(_) => Ok((
None,
UntaggedValue::binary(bytes),
Tag {
span,
anchor: Some(AnchorLocation::File(
cwd.to_string_lossy().to_string(),
)),
},
)),
}
} else {
Ok((
None,
UntaggedValue::binary(bytes),
Tag {
span,
anchor: Some(AnchorLocation::File(
cwd.to_string_lossy().to_string(),
)),
},
))
}
}
_ => Ok((
None,
UntaggedValue::binary(bytes),
Tag {
span,
anchor: Some(AnchorLocation::File(
cwd.to_string_lossy().to_string(),
)),
},
)),
}
}
},
Err(_) => Err(ShellError::labeled_error(
"File could not be opened",
"file not found",
span,
)),
}
} else {
Err(ShellError::labeled_error(
"File could not be opened",
"file not found",
span,
))
}
*/
}
fn convert_via_utf8(

View File

@ -100,8 +100,8 @@ impl WholeStreamCommand for SkipUntil {
trace!("RESULT = {:?}", result);
match result {
Ok(ref v) if v.is_true() => true,
_ => false,
Ok(ref v) if v.is_true() => false, // stop skipping
_ => true,
}
}
})

View File

@ -1,4 +1,5 @@
use crate::commands::WholeStreamCommand;
use crate::data::base::coerce_compare;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
@ -70,15 +71,33 @@ async fn sort_by(
let (SortByArgs { rest }, mut input) = args.process(&registry).await?;
let mut vec = input.drain_vec().await;
sort(&mut vec, &rest, &tag)?;
let mut values_vec_deque: VecDeque<Value> = VecDeque::new();
for item in vec {
values_vec_deque.push_back(item);
}
Ok(futures::stream::iter(values_vec_deque).to_output_stream())
}
pub fn sort(
vec: &mut [Value],
keys: &[Tagged<String>],
tag: impl Into<Tag>,
) -> Result<(), ShellError> {
let tag = tag.into();
if vec.is_empty() {
return Err(ShellError::labeled_error(
"Error performing sort-by command",
"sort-by error",
"no values to work with",
"no values to work with",
tag,
));
}
for sort_arg in rest.iter() {
for sort_arg in keys.iter() {
let match_test = get_data_by_key(&vec[0], sort_arg.borrow_spanned());
if match_test == None {
return Err(ShellError::labeled_error(
@ -94,11 +113,11 @@ async fn sort_by(
value: UntaggedValue::Primitive(_),
..
} => {
vec.sort();
vec.sort_by(|a, b| coerce_compare(a, b).expect("Unimplemented BUG: What about primitives that don't have an order defined?").compare());
}
_ => {
let calc_key = |item: &Value| {
rest.iter()
keys.iter()
.map(|f| get_data_by_key(item, f.borrow_spanned()))
.collect::<Vec<Option<Value>>>()
};
@ -106,13 +125,7 @@ async fn sort_by(
}
};
let mut values_vec_deque: VecDeque<Value> = VecDeque::new();
for item in vec {
values_vec_deque.push_back(item);
}
Ok(futures::stream::iter(values_vec_deque).to_output_stream())
Ok(())
}
#[cfg(test)]

View File

@ -1,16 +1,15 @@
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{
Signature, SpannedTypeName, SyntaxShape, TaggedDictBuilder, UntaggedValue, Value,
};
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, Value};
use nu_source::Tagged;
use nu_value_ext::as_string;
pub struct SplitBy;
#[derive(Deserialize)]
pub struct SplitByArgs {
column_name: Tagged<String>,
column_name: Option<Tagged<String>>,
}
#[async_trait]
@ -20,7 +19,7 @@ impl WholeStreamCommand for SplitBy {
}
fn signature(&self) -> Signature {
Signature::build("split-by").required(
Signature::build("split-by").optional(
"column_name",
SyntaxShape::String,
"the name of the column within the nested table to split by",
@ -53,108 +52,84 @@ pub async fn split_by(
return Err(ShellError::labeled_error(
"Expected table from pipeline",
"requires a table input",
column_name.span(),
name,
));
}
match split(&column_name, &values[0], name) {
Ok(split) => Ok(OutputStream::one(split)),
match split(&column_name, &values[0], &name) {
Ok(splits) => Ok(OutputStream::one(ReturnSuccess::value(splits))),
Err(err) => Err(err),
}
}
enum Grouper {
ByColumn(Option<Tagged<String>>),
}
pub fn split(
column_name: &Tagged<String>,
value: &Value,
column_name: &Option<Tagged<String>>,
values: &Value,
tag: impl Into<Tag>,
) -> Result<Value, ShellError> {
let origin_tag = tag.into();
let name = tag.into();
let mut splits = indexmap::IndexMap::new();
let grouper = if let Some(column_name) = column_name {
Grouper::ByColumn(Some(column_name.clone()))
} else {
Grouper::ByColumn(None)
};
match value {
Value {
value: UntaggedValue::Row(group_sets),
..
} => {
for (group_key, group_value) in group_sets.entries.iter() {
match *group_value {
Value {
value: UntaggedValue::Table(ref dataset),
..
} => {
let group = crate::commands::group_by::group(
&column_name,
dataset.to_vec(),
&origin_tag,
)?;
match group {
Value {
value: UntaggedValue::Row(o),
..
} => {
for (split_label, subset) in o.entries.into_iter() {
match subset {
Value {
value: UntaggedValue::Table(subset),
tag,
} => {
let s = splits
.entry(split_label.clone())
.or_insert(indexmap::IndexMap::new());
s.insert(
group_key.clone(),
UntaggedValue::table(&subset).into_value(tag),
);
}
other => {
return Err(ShellError::type_error(
"a table value",
other.spanned_type_name(),
))
}
}
}
}
_ => {
return Err(ShellError::type_error(
"a table value",
group.spanned_type_name(),
))
}
}
}
ref other => {
return Err(ShellError::type_error(
"a table value",
other.spanned_type_name(),
))
}
match grouper {
Grouper::ByColumn(Some(column_name)) => {
let block = Box::new(move |row: &Value| {
match row.get_data_by_key(column_name.borrow_spanned()) {
Some(group_key) => Ok(as_string(&group_key)?),
None => Err(suggestions(column_name.borrow_tagged(), &row)),
}
}
});
crate::utils::data::split(&values, &Some(block), &name)
}
_ => {
return Err(ShellError::type_error(
"a table value",
value.spanned_type_name(),
))
Grouper::ByColumn(None) => {
let block = Box::new(move |row: &Value| match as_string(row) {
Ok(group_key) => Ok(group_key),
Err(reason) => Err(reason),
});
crate::utils::data::split(&values, &Some(block), &name)
}
}
let mut out = TaggedDictBuilder::new(&origin_tag);
for (k, v) in splits.into_iter() {
out.insert_untagged(k, UntaggedValue::row(v));
}
Ok(out.into_value())
}
pub fn suggestions(tried: Tagged<&str>, for_value: &Value) -> ShellError {
let possibilities = for_value.data_descriptors();
let mut possible_matches: Vec<_> = possibilities
.iter()
.map(|x| (natural::distance::levenshtein_distance(x, &tried), x))
.collect();
possible_matches.sort();
if !possible_matches.is_empty() {
ShellError::labeled_error(
"Unknown column",
format!("did you mean '{}'?", possible_matches[0].1),
tried.tag(),
)
} else {
ShellError::labeled_error(
"Unknown column",
"row does not contain this column",
tried.tag(),
)
}
}
#[cfg(test)]
mod tests {
use super::split;
use crate::commands::group_by::group;
use crate::commands::split_by::split;
use indexmap::IndexMap;
use nu_errors::ShellError;
use nu_protocol::{UntaggedValue, Value};
@ -173,11 +148,12 @@ mod tests {
}
fn nu_releases_grouped_by_date() -> Result<Value, ShellError> {
let key = String::from("date").tagged_unknown();
group(&key, nu_releases_commiters(), Tag::unknown())
let key = Some(String::from("date").tagged_unknown());
let sample = table(&nu_releases_committers());
group(&key, &sample, Tag::unknown())
}
fn nu_releases_commiters() -> Vec<Value> {
fn nu_releases_committers() -> Vec<Value> {
vec![
row(
indexmap! {"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => string("August 23-2019")},
@ -211,7 +187,7 @@ mod tests {
#[test]
fn splits_inner_tables_by_key() -> Result<(), ShellError> {
let for_key = String::from("country").tagged_unknown();
let for_key = Some(String::from("country").tagged_unknown());
assert_eq!(
split(&for_key, &nu_releases_grouped_by_date()?, Tag::unknown())?,
@ -257,7 +233,7 @@ mod tests {
#[test]
fn errors_if_key_within_some_inner_table_is_missing() {
let for_key = String::from("country").tagged_unknown();
let for_key = Some(String::from("country").tagged_unknown());
let nu_releases = row(indexmap! {
"August 23-2019".into() => table(&[

View File

@ -78,7 +78,7 @@ async fn t_sort_by(
let values: Vec<Value> = input.collect().await;
let column_grouped_by_name = if let Some(grouped_by) = group_by {
Some(grouped_by.item().clone())
Some(grouped_by)
} else {
None
};

View File

@ -1,8 +1,9 @@
use crate::commands::WholeStreamCommand;
use crate::format::TableView;
use crate::data::value::{format_leaf, style_leaf};
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{Primitive, Signature, SyntaxShape, UntaggedValue, Value};
use nu_table::{draw_table, Alignment, StyledString, TextStyle, Theme};
use std::time::Instant;
const STREAM_PAGE_SIZE: usize = 1000;
@ -38,12 +39,188 @@ impl WholeStreamCommand for Table {
}
}
fn str_to_color(s: String) -> Option<ansi_term::Color> {
match s.as_str() {
"g" | "green" => Some(ansi_term::Color::Green),
"r" | "red" => Some(ansi_term::Color::Red),
"u" | "blue" => Some(ansi_term::Color::Blue),
"b" | "black" => Some(ansi_term::Color::Black),
"y" | "yellow" => Some(ansi_term::Color::Yellow),
"p" | "purple" => Some(ansi_term::Color::Purple),
"c" | "cyan" => Some(ansi_term::Color::Cyan),
"w" | "white" => Some(ansi_term::Color::White),
_ => None,
}
}
pub fn from_list(values: &[Value], starting_idx: usize) -> nu_table::Table {
let config = crate::data::config::config(Tag::unknown());
let header_style = if let Ok(config) = &config {
let header_align = config.get("header_align").map_or(Alignment::Left, |a| {
a.as_string()
.map_or(Alignment::Center, |a| match a.to_lowercase().as_str() {
"center" | "c" => Alignment::Center,
"right" | "r" => Alignment::Right,
_ => Alignment::Center,
})
});
let header_color = match config.get("header_color") {
Some(c) => match c.as_string() {
Ok(color) => str_to_color(color.to_lowercase()).unwrap_or(ansi_term::Color::Green),
_ => ansi_term::Color::Green,
},
_ => ansi_term::Color::Green,
};
let header_bold = match config.get("header_bold") {
Some(b) => match b.as_bool() {
Ok(b) => b,
_ => true,
},
_ => true,
};
TextStyle {
alignment: header_align,
color: Some(header_color),
is_bold: header_bold,
}
} else {
TextStyle::default_header()
};
let mut headers: Vec<StyledString> = nu_protocol::merge_descriptors(values)
.into_iter()
.map(|x| StyledString::new(x, header_style.clone()))
.collect();
let entries = values_to_entries(values, &mut headers, starting_idx);
if let Ok(config) = config {
if let Some(style) = config.get("table_mode") {
if let Ok(table_mode) = style.as_string() {
if table_mode == "light" {
return nu_table::Table {
headers,
data: entries,
theme: Theme::light(),
};
}
}
}
}
nu_table::Table {
headers,
data: entries,
theme: Theme::compact(),
}
}
fn are_table_indexes_disabled() -> bool {
let config = crate::data::config::config(Tag::unknown());
match config {
Ok(config) => {
let disable_indexes = config.get("disable_table_indexes");
disable_indexes.map_or(false, |x| x.as_bool().unwrap_or(false))
}
_ => false,
}
}
fn values_to_entries(
values: &[Value],
headers: &mut Vec<StyledString>,
starting_idx: usize,
) -> Vec<Vec<StyledString>> {
let disable_indexes = are_table_indexes_disabled();
let mut entries = vec![];
if headers.is_empty() {
headers.push(StyledString::new("".to_string(), TextStyle::basic()));
}
for (idx, value) in values.iter().enumerate() {
let mut row: Vec<StyledString> = headers
.iter()
.map(|d: &StyledString| {
if d.contents == "" {
match value {
Value {
value: UntaggedValue::Row(..),
..
} => StyledString::new(
format_leaf(&UntaggedValue::nothing()).plain_string(100_000),
style_leaf(&UntaggedValue::nothing()),
),
_ => StyledString::new(
format_leaf(value).plain_string(100_000),
style_leaf(value),
),
}
} else {
match value {
Value {
value: UntaggedValue::Row(..),
..
} => {
let data = value.get_data(&d.contents);
StyledString::new(
format_leaf(data.borrow()).plain_string(100_000),
style_leaf(data.borrow()),
)
}
_ => StyledString::new(
format_leaf(&UntaggedValue::nothing()).plain_string(100_000),
style_leaf(&UntaggedValue::nothing()),
),
}
}
})
.collect();
// Indices are green, bold, right-aligned:
if !disable_indexes {
row.insert(
0,
StyledString::new(
(starting_idx + idx).to_string(),
TextStyle {
alignment: Alignment::Center,
color: Some(ansi_term::Color::Green),
is_bold: true,
},
),
);
}
entries.push(row);
}
if !disable_indexes {
headers.insert(
0,
StyledString::new(
"#".to_owned(),
TextStyle {
alignment: Alignment::Center,
color: Some(ansi_term::Color::Green),
is_bold: true,
},
),
);
}
entries
}
async fn table(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let registry = registry.clone();
let mut args = args.evaluate_once(&registry).await?;
let mut finished = false;
let host = args.host.clone();
// let host = args.host.clone();
let mut start_number = match args.get("start_number") {
Some(Value {
value: UntaggedValue::Primitive(Primitive::Int(i)),
@ -64,6 +241,8 @@ async fn table(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputSt
let mut delay_slot = None;
let termwidth = std::cmp::max(textwrap::termwidth(), 20);
while !finished {
let mut new_input: VecDeque<Value> = VecDeque::new();
@ -113,12 +292,9 @@ async fn table(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputSt
let input: Vec<Value> = new_input.into();
if !input.is_empty() {
let mut host = host.lock();
let view = TableView::from_list(&input, start_number);
let t = from_list(&input, start_number);
if let Some(view) = view {
handle_unexpected(&mut *host, |host| crate::format::print_view(&view, host));
}
draw_table(&t, termwidth);
}
start_number += input.len();
@ -126,15 +302,3 @@ async fn table(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputSt
Ok(OutputStream::empty())
}
#[cfg(test)]
mod tests {
use super::Table;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Table {})
}
}

View File

@ -1,9 +1,9 @@
use crate::commands::WholeStreamCommand;
use crate::context::CommandRegistry;
use crate::prelude::*;
use indexmap::set::IndexSet;
use indexmap::map::IndexMap;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature};
use nu_protocol::Signature;
pub struct Uniq;
@ -14,7 +14,7 @@ impl WholeStreamCommand for Uniq {
}
fn signature(&self) -> Signature {
Signature::build("uniq")
Signature::build("uniq").switch("count", "Count the unique rows", Some('c'))
}
fn usage(&self) -> &str {
@ -30,17 +30,66 @@ impl WholeStreamCommand for Uniq {
}
}
async fn uniq(args: CommandArgs, _registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
async fn uniq(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let args = args.evaluate_once(&registry).await?;
let should_show_count = args.has("count");
let input = args.input;
let uniq_values: IndexSet<_> = input.collect().await;
let uniq_values = {
let mut counter = IndexMap::<nu_protocol::Value, usize>::new();
for line in input.into_vec().await {
*counter.entry(line).or_insert(0) += 1;
}
counter
};
let mut values_vec_deque = VecDeque::new();
for item in uniq_values
.iter()
.map(|row| ReturnSuccess::value(row.clone()))
{
values_vec_deque.push_back(item);
if should_show_count {
for item in uniq_values {
use nu_protocol::{UntaggedValue, Value};
let value = {
match item.0.value {
UntaggedValue::Row(mut row) => {
row.entries.insert(
"count".to_string(),
UntaggedValue::int(item.1).into_untagged_value(),
);
Value {
value: UntaggedValue::Row(row),
tag: item.0.tag,
}
}
UntaggedValue::Primitive(p) => {
let mut map = IndexMap::<String, Value>::new();
map.insert(
"value".to_string(),
UntaggedValue::Primitive(p).into_untagged_value(),
);
map.insert(
"count".to_string(),
UntaggedValue::int(item.1).into_untagged_value(),
);
Value {
value: UntaggedValue::row(map),
tag: item.0.tag,
}
}
UntaggedValue::Table(_) => {
return Err(ShellError::labeled_error(
"uniq -c cannot operate on tables.",
"source",
item.0.tag.span,
))
}
UntaggedValue::Error(_) | UntaggedValue::Block(_) => item.0,
}
};
values_vec_deque.push_back(value);
}
} else {
for item in uniq_values {
values_vec_deque.push_back(item.0);
}
}
Ok(futures::stream::iter(values_vec_deque).to_output_stream())

View File

@ -1,6 +1,6 @@
pub(crate) mod base;
pub(crate) mod command;
pub(crate) mod config;
pub mod config;
pub(crate) mod dict;
pub(crate) mod files;
pub mod primitive;

View File

@ -103,7 +103,7 @@ pub fn read(
}
}
pub(crate) fn config(tag: impl Into<Tag>) -> Result<IndexMap<String, Value>, ShellError> {
pub fn config(tag: impl Into<Tag>) -> Result<IndexMap<String, Value>, ShellError> {
read(tag, &None)
}

View File

@ -6,7 +6,7 @@ use parking_lot::Mutex;
use std::fmt::Debug;
use std::sync::Arc;
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Default)]
pub struct NuConfig {
pub vars: Arc<Mutex<IndexMap<String, Value>>>,
}

View File

@ -1,4 +1,5 @@
use nu_protocol::{hir::Number, Primitive};
use nu_table::TextStyle;
pub fn number(number: impl Into<Number>) -> Primitive {
let number = number.into();
@ -9,9 +10,9 @@ pub fn number(number: impl Into<Number>) -> Primitive {
}
}
pub fn style_primitive(primitive: &Primitive) -> &'static str {
pub fn style_primitive(primitive: &Primitive) -> TextStyle {
match primitive {
Primitive::Int(_) | Primitive::Bytes(_) | Primitive::Decimal(_) => "r",
_ => "",
Primitive::Int(_) | Primitive::Bytes(_) | Primitive::Decimal(_) => TextStyle::basic_right(),
_ => TextStyle::basic(),
}
}

View File

@ -7,6 +7,8 @@ use nu_protocol::hir::Operator;
use nu_protocol::ShellTypeName;
use nu_protocol::{Primitive, Type, UntaggedValue};
use nu_source::{DebugDocBuilder, PrettyDebug, Tagged};
use nu_table::TextStyle;
use num_traits::Zero;
pub fn date_from_str(s: Tagged<&str>) -> Result<UntaggedValue, ShellError> {
let date = DateTime::parse_from_rfc3339(s.item).map_err(|err| {
@ -34,6 +36,10 @@ pub fn merge_values(
}
}
fn zero_division_error() -> UntaggedValue {
UntaggedValue::Error(ShellError::untagged_runtime_error("division by zero"))
}
pub fn compute_values(
operator: Operator,
left: &UntaggedValue,
@ -54,7 +60,9 @@ pub fn compute_values(
Operator::Minus => Ok(UntaggedValue::Primitive(Primitive::Int(x - y))),
Operator::Multiply => Ok(UntaggedValue::Primitive(Primitive::Int(x * y))),
Operator::Divide => {
if x - (y * (x / y)) == num_bigint::BigInt::from(0) {
if y.is_zero() {
Ok(zero_division_error())
} else if x - (y * (x / y)) == num_bigint::BigInt::from(0) {
Ok(UntaggedValue::Primitive(Primitive::Int(x / y)))
} else {
Ok(UntaggedValue::Primitive(Primitive::Decimal(
@ -70,7 +78,12 @@ pub fn compute_values(
Operator::Plus => Ok(x + bigdecimal::BigDecimal::from(y.clone())),
Operator::Minus => Ok(x - bigdecimal::BigDecimal::from(y.clone())),
Operator::Multiply => Ok(x * bigdecimal::BigDecimal::from(y.clone())),
Operator::Divide => Ok(x / bigdecimal::BigDecimal::from(y.clone())),
Operator::Divide => {
if y.is_zero() {
return Ok(zero_division_error());
}
Ok(x / bigdecimal::BigDecimal::from(y.clone()))
}
_ => Err((left.type_name(), right.type_name())),
}?;
Ok(UntaggedValue::Primitive(Primitive::Decimal(result)))
@ -80,7 +93,12 @@ pub fn compute_values(
Operator::Plus => Ok(bigdecimal::BigDecimal::from(x.clone()) + y),
Operator::Minus => Ok(bigdecimal::BigDecimal::from(x.clone()) - y),
Operator::Multiply => Ok(bigdecimal::BigDecimal::from(x.clone()) * y),
Operator::Divide => Ok(bigdecimal::BigDecimal::from(x.clone()) / y),
Operator::Divide => {
if y.is_zero() {
return Ok(zero_division_error());
}
Ok(bigdecimal::BigDecimal::from(x.clone()) / y)
}
_ => Err((left.type_name(), right.type_name())),
}?;
Ok(UntaggedValue::Primitive(Primitive::Decimal(result)))
@ -90,7 +108,12 @@ pub fn compute_values(
Operator::Plus => Ok(x + y),
Operator::Minus => Ok(x - y),
Operator::Multiply => Ok(x * y),
Operator::Divide => Ok(x / y),
Operator::Divide => {
if y.is_zero() {
return Ok(zero_division_error());
}
Ok(x / y)
}
_ => Err((left.type_name(), right.type_name())),
}?;
Ok(UntaggedValue::Primitive(Primitive::Decimal(result)))
@ -160,10 +183,10 @@ pub fn format_leaf<'a>(value: impl Into<&'a UntaggedValue>) -> DebugDocBuilder {
InlineShape::from_value(value.into()).format().pretty()
}
pub fn style_leaf<'a>(value: impl Into<&'a UntaggedValue>) -> &'static str {
pub fn style_leaf<'a>(value: impl Into<&'a UntaggedValue>) -> TextStyle {
match value.into() {
UntaggedValue::Primitive(p) => style_primitive(p),
_ => "",
_ => TextStyle::basic(),
}
}

View File

@ -71,7 +71,7 @@ impl Environment {
fn remove_env(&mut self, key: &str) {
if let Some(Value {
value: UntaggedValue::Row(ref mut envs),
tag: _,
..
}) = self.environment_vars
{
envs.entries.remove(key);

View File

@ -1,7 +1,7 @@
use crate::prelude::*;
#[cfg(test)]
use indexmap::IndexMap;
use nu_errors::ShellError;
// use nu_errors::ShellError;
use std::ffi::OsString;
use std::fmt::Debug;
@ -200,13 +200,13 @@ impl Host for FakeHost {
}
}
pub(crate) fn handle_unexpected<T>(
host: &mut dyn Host,
func: impl FnOnce(&mut dyn Host) -> Result<T, ShellError>,
) {
let result = func(host);
// pub(crate) fn handle_unexpected<T>(
// host: &mut dyn Host,
// func: impl FnOnce(&mut dyn Host) -> Result<T, ShellError>,
// ) {
// let result = func(host);
if let Err(err) = result {
host.stderr(&format!("Something unexpected happened:\n{:?}", err));
}
}
// if let Err(err) = result {
// host.stderr(&format!("Something unexpected happened:\n{:?}", err));
// }
// }

View File

@ -47,7 +47,10 @@ pub(crate) async fn evaluate_baseline_expr(
match binary.op.expr {
Expression::Literal(hir::Literal::Operator(op)) => {
match apply_operator(op, &left, &right) {
Ok(result) => Ok(result.into_value(tag)),
Ok(result) => match result {
UntaggedValue::Error(shell_err) => Err(shell_err),
_ => Ok(result.into_value(tag)),
},
Err((left_type, right_type)) => Err(ShellError::coerce_error(
left_type.spanned(binary.left.span),
right_type.spanned(binary.right.span),

View File

@ -1,4 +1,5 @@
use crate::data::value;
use nu_errors::ShellError;
use nu_protocol::hir::Operator;
use nu_protocol::{Primitive, ShellTypeName, UntaggedValue, Value};
use std::ops::Not;
@ -24,7 +25,14 @@ pub fn apply_operator(
Operator::Plus => value::compute_values(op, left, right),
Operator::Minus => value::compute_values(op, left, right),
Operator::Multiply => value::compute_values(op, left, right),
Operator::Divide => value::compute_values(op, left, right),
Operator::Divide => value::compute_values(op, left, right).map(|res| match res {
UntaggedValue::Error(_) => UntaggedValue::Error(ShellError::labeled_error(
"Evaluation error",
"division by zero",
&right.tag.span,
)),
_ => res,
}),
Operator::In => table_contains(left, right).map(UntaggedValue::boolean),
Operator::NotIn => table_contains(left, right).map(|x| UntaggedValue::boolean(!x)),
Operator::And => match (left.as_bool(), right.as_bool()) {

View File

@ -1,14 +1,6 @@
pub(crate) mod table;
use crate::prelude::*;
use nu_errors::ShellError;
pub(crate) use table::TableView;
pub(crate) trait RenderView {
fn render_view(&self, host: &mut dyn Host) -> Result<(), ShellError>;
}
pub(crate) fn print_view(view: &impl RenderView, host: &mut dyn Host) -> Result<(), ShellError> {
view.render_view(host)
}

View File

@ -1,453 +0,0 @@
use crate::data::value::{format_leaf, style_leaf};
use crate::format::RenderView;
use crate::prelude::*;
use derive_new::new;
use nu_errors::ShellError;
use nu_protocol::{UntaggedValue, Value};
use textwrap::fill;
use prettytable::format::{Alignment, FormatBuilder, LinePosition, LineSeparator};
use prettytable::{color, Attr, Cell, Row, Table};
type Entries = Vec<Vec<(String, &'static str)>>;
#[derive(Debug, new)]
pub struct TableView {
// List of header cell values:
headers: Vec<String>,
// List of rows of cells, each containing value and prettytable style-string:
entries: Entries,
}
enum TableMode {
Light,
Normal,
}
impl TableView {
pub fn from_list(values: &[Value], starting_idx: usize) -> Option<TableView> {
if values.is_empty() {
return None;
}
// Different platforms want different amounts of buffer, not sure why
let termwidth = std::cmp::max(textwrap::termwidth(), 20);
let mut headers = nu_protocol::merge_descriptors(values);
let mut entries = values_to_entries(values, &mut headers, starting_idx);
let max_per_column = max_per_column(&headers, &entries, values.len());
maybe_truncate_columns(&mut headers, &mut entries, termwidth);
let headers_len = headers.len();
// Measure how big our columns need to be (accounting for separators also)
let max_naive_column_width = (termwidth - 3 * (headers_len - 1)) / headers_len;
let column_space =
ColumnSpace::measure(&max_per_column, max_naive_column_width, headers_len);
// This gives us the max column width
let max_column_width = column_space.max_width(termwidth);
// This width isn't quite right, as we're rounding off some of our space
let column_space = column_space.fix_almost_column_width(
&max_per_column,
max_naive_column_width,
max_column_width,
headers_len,
);
// This should give us the final max column width
let max_column_width = column_space.max_width(termwidth);
// Wrap cells as needed
let table_view = wrap_cells(
headers,
entries,
max_per_column,
max_naive_column_width,
max_column_width,
);
Some(table_view)
}
}
fn are_table_indexes_disabled() -> bool {
let config = crate::data::config::config(Tag::unknown());
match config {
Ok(config) => {
let disable_indexes = config.get("disable_table_indexes");
disable_indexes.map_or(false, |x| x.as_bool().unwrap_or(false))
}
_ => false,
}
}
fn values_to_entries(values: &[Value], headers: &mut Vec<String>, starting_idx: usize) -> Entries {
let disable_indexes = are_table_indexes_disabled();
let mut entries = vec![];
if headers.is_empty() {
headers.push("".to_string());
}
for (idx, value) in values.iter().enumerate() {
let mut row: Vec<(String, &'static str)> = headers
.iter()
.map(|d: &String| {
if d == "" {
match value {
Value {
value: UntaggedValue::Row(..),
..
} => (
format_leaf(&UntaggedValue::nothing()).plain_string(100_000),
style_leaf(&UntaggedValue::nothing()),
),
_ => (format_leaf(value).plain_string(100_000), style_leaf(value)),
}
} else {
match value {
Value {
value: UntaggedValue::Row(..),
..
} => {
let data = value.get_data(d);
(
format_leaf(data.borrow()).plain_string(100_000),
style_leaf(data.borrow()),
)
}
_ => (
format_leaf(&UntaggedValue::nothing()).plain_string(100_000),
style_leaf(&UntaggedValue::nothing()),
),
}
}
})
.collect();
// Indices are green, bold, right-aligned:
if !disable_indexes {
row.insert(0, ((starting_idx + idx).to_string(), "Fgbr"));
}
entries.push(row);
}
if !disable_indexes {
headers.insert(0, "#".to_owned());
}
entries
}
#[allow(clippy::ptr_arg)]
fn max_per_column(headers: &[String], entries: &Entries, values_len: usize) -> Vec<usize> {
let mut max_per_column = vec![];
for i in 0..headers.len() {
let mut current_col_max = 0;
let iter = entries.iter().take(values_len);
for entry in iter {
let value_length = entry[i].0.chars().count();
if value_length > current_col_max {
current_col_max = value_length;
}
}
max_per_column.push(std::cmp::max(current_col_max, headers[i].chars().count()));
}
max_per_column
}
fn maybe_truncate_columns(headers: &mut Vec<String>, entries: &mut Entries, termwidth: usize) {
// Make sure we have enough space for the columns we have
let max_num_of_columns = termwidth / 10;
// If we have too many columns, truncate the table
if max_num_of_columns < headers.len() {
headers.truncate(max_num_of_columns);
for entry in entries.iter_mut() {
entry.truncate(max_num_of_columns);
}
headers.push("...".to_owned());
for entry in entries.iter_mut() {
entry.push(("...".to_owned(), "c")); // ellipsis is centred
}
}
}
struct ColumnSpace {
num_overages: usize,
underage_sum: usize,
overage_separator_sum: usize,
}
impl ColumnSpace {
/// Measure how much space we have once we subtract off the columns who are small enough
fn measure(
max_per_column: &[usize],
max_naive_column_width: usize,
headers_len: usize,
) -> ColumnSpace {
let mut num_overages = 0;
let mut underage_sum = 0;
let mut overage_separator_sum = 0;
let iter = max_per_column.iter().enumerate().take(headers_len);
for (i, &column_max) in iter {
if column_max > max_naive_column_width {
num_overages += 1;
if i != (headers_len - 1) {
overage_separator_sum += 3;
}
if i == 0 {
overage_separator_sum += 1;
}
} else {
underage_sum += column_max;
// if column isn't last, add 3 for its separator
if i != (headers_len - 1) {
underage_sum += 3;
}
if i == 0 {
underage_sum += 1;
}
}
}
ColumnSpace {
num_overages,
underage_sum,
overage_separator_sum,
}
}
fn fix_almost_column_width(
self,
max_per_column: &[usize],
max_naive_column_width: usize,
max_column_width: usize,
headers_len: usize,
) -> ColumnSpace {
let mut num_overages = 0;
let mut overage_separator_sum = 0;
let mut underage_sum = self.underage_sum;
let iter = max_per_column.iter().enumerate().take(headers_len);
for (i, &column_max) in iter {
if column_max > max_naive_column_width {
if column_max <= max_column_width {
underage_sum += column_max;
// if column isn't last, add 3 for its separator
if i != (headers_len - 1) {
underage_sum += 3;
}
if i == 0 {
underage_sum += 1;
}
} else {
// Column is still too large, so let's count it
num_overages += 1;
if i != (headers_len - 1) {
overage_separator_sum += 3;
}
if i == 0 {
overage_separator_sum += 1;
}
}
}
}
ColumnSpace {
num_overages,
underage_sum,
overage_separator_sum,
}
}
fn max_width(&self, termwidth: usize) -> usize {
let ColumnSpace {
num_overages,
underage_sum,
overage_separator_sum,
} = self;
if *num_overages > 0 {
(termwidth - 1 - *underage_sum - *overage_separator_sum) / *num_overages
} else {
99999
}
}
}
fn wrap_cells(
mut headers: Vec<String>,
mut entries: Entries,
max_per_column: Vec<usize>,
max_naive_column_width: usize,
max_column_width: usize,
) -> TableView {
for head in 0..headers.len() {
if max_per_column[head] > max_naive_column_width {
headers[head] = fill(&headers[head], max_column_width);
for entry in entries.iter_mut() {
entry[head].0 = fill(&entry[head].0, max_column_width);
}
}
}
TableView { headers, entries }
}
impl RenderView for TableView {
fn render_view(&self, host: &mut dyn Host) -> Result<(), ShellError> {
if self.entries.is_empty() {
return Ok(());
}
let mut table = Table::new();
let mut config = crate::data::config::config(Tag::unknown())?;
let header_align = config.get("header_align").map_or(Alignment::LEFT, |a| {
a.as_string()
.map_or(Alignment::LEFT, |a| match a.to_lowercase().as_str() {
"center" | "c" => Alignment::CENTER,
"right" | "r" => Alignment::RIGHT,
_ => Alignment::LEFT,
})
});
let header_color = config.get("header_color").map_or(color::GREEN, |c| {
c.as_string().map_or(color::GREEN, |c| {
str_to_color(c.to_lowercase()).unwrap_or(color::GREEN)
})
});
let header_style =
config
.remove("header_style")
.map_or(vec![Attr::Bold], |y| match y.value {
UntaggedValue::Table(t) => to_style_vec(t),
UntaggedValue::Primitive(p) => vec![p
.into_string(Span::unknown())
.map_or(Attr::Bold, |s| str_to_style(s).unwrap_or(Attr::Bold))],
_ => vec![Attr::Bold],
});
let table_mode = if let Some(s) = config.get("table_mode") {
match s.as_string() {
Ok(typ) if typ == "light" => TableMode::Light,
_ => TableMode::Normal,
}
} else {
TableMode::Normal
};
match table_mode {
TableMode::Light => {
table.set_format(
FormatBuilder::new()
.separator(LinePosition::Title, LineSeparator::new('─', '─', ' ', ' '))
.separator(LinePosition::Bottom, LineSeparator::new(' ', ' ', ' ', ' '))
.padding(1, 1)
.build(),
);
}
_ => {
table.set_format(
FormatBuilder::new()
.column_separator('│')
.separator(LinePosition::Top, LineSeparator::new('─', '┬', ' ', ' '))
.separator(LinePosition::Title, LineSeparator::new('─', '┼', ' ', ' '))
.separator(LinePosition::Bottom, LineSeparator::new('─', '┴', ' ', ' '))
.padding(1, 1)
.build(),
);
}
}
let skip_headers = (self.headers.len() == 2 && self.headers[1] == "")
|| (self.headers.len() == 1 && self.headers[0] == "");
let header: Vec<Cell> = self
.headers
.iter()
.map(|h| {
let mut c = Cell::new_align(h, header_align)
.with_style(Attr::ForegroundColor(header_color));
for &s in &header_style {
c.style(s);
}
c
})
.collect();
if !skip_headers {
table.set_titles(Row::new(header));
}
for row in &self.entries {
table.add_row(Row::new(
row.iter()
.map(|(v, s)| Cell::new(v).style_spec(s))
.collect(),
));
}
table.print_term(&mut *host.out_terminal().ok_or_else(|| ShellError::untagged_runtime_error("Could not open terminal for output"))?)
.map_err(|_| ShellError::untagged_runtime_error("Internal error: could not print to terminal (for unix systems check to make sure TERM is set)"))?;
Ok(())
}
}
fn str_to_color(s: String) -> Option<color::Color> {
match s.as_str() {
"g" | "green" => Some(color::GREEN),
"r" | "red" => Some(color::RED),
"u" | "blue" => Some(color::BLUE),
"b" | "black" => Some(color::BLACK),
"y" | "yellow" => Some(color::YELLOW),
"m" | "magenta" => Some(color::MAGENTA),
"c" | "cyan" => Some(color::CYAN),
"w" | "white" => Some(color::WHITE),
"bg" | "bright green" => Some(color::BRIGHT_GREEN),
"br" | "bright red" => Some(color::BRIGHT_RED),
"bu" | "bright blue" => Some(color::BRIGHT_BLUE),
"by" | "bright yellow" => Some(color::BRIGHT_YELLOW),
"bm" | "bright magenta" => Some(color::BRIGHT_MAGENTA),
"bc" | "bright cyan" => Some(color::BRIGHT_CYAN),
"bw" | "bright white" => Some(color::BRIGHT_WHITE),
_ => None,
}
}
fn to_style_vec(a: Vec<Value>) -> Vec<Attr> {
let mut v: Vec<Attr> = Vec::new();
for t in a {
if let Ok(s) = t.as_string() {
if let Some(r) = str_to_style(s) {
v.push(r);
}
}
}
v
}
fn str_to_style(s: String) -> Option<Attr> {
match s.as_str() {
"b" | "bold" => Some(Attr::Bold),
"i" | "italic" | "italics" => Some(Attr::Italic(true)),
"u" | "underline" | "underlined" => Some(Attr::Underline(true)),
_ => None,
}
}

View File

@ -16,7 +16,7 @@ extern crate quickcheck_macros;
mod cli;
mod commands;
mod context;
mod data;
pub mod data;
mod deserializer;
mod env;
mod evaluate;
@ -32,13 +32,15 @@ pub mod utils;
mod examples;
pub use crate::cli::{
cli, create_default_context, load_plugins, run_pipeline_standalone, run_vec_of_pipelines,
cli, create_default_context, load_plugins, process_line, run_pipeline_standalone,
run_vec_of_pipelines, LineResult,
};
pub use crate::commands::command::{
whole_stream_command, CommandArgs, EvaluatedWholeStreamCommandArgs, WholeStreamCommand,
};
pub use crate::commands::help::get_help;
pub use crate::context::CommandRegistry;
pub use crate::data::config;
pub use crate::data::dict::TaggedListBuilder;
pub use crate::data::primitive;
pub use crate::data::value;

View File

@ -77,7 +77,7 @@ pub(crate) use crate::context::CommandRegistry;
pub(crate) use crate::context::Context;
pub(crate) use crate::data::config;
pub(crate) use crate::data::value;
pub(crate) use crate::env::host::handle_unexpected;
// pub(crate) use crate::env::host::handle_unexpected;
pub(crate) use crate::env::Host;
pub(crate) use crate::shell::filesystem_shell::FilesystemShell;
pub(crate) use crate::shell::help_shell::HelpShell;

View File

@ -1,61 +1,28 @@
use indexmap::IndexMap;
use nu_errors::ShellError;
use nu_protocol::{TaggedDictBuilder, UntaggedValue, Value};
use nu_source::{Tag, Tagged, TaggedItem};
use nu_value_ext::{as_string, get_data_by_key};
use nu_source::Tag;
use nu_value_ext::as_string;
#[allow(clippy::type_complexity)]
pub fn group(
column_name: Option<Tagged<String>>,
values: &[Value],
grouper: Option<Box<dyn Fn(&Value) -> Result<String, ShellError> + Send>>,
values: &Value,
grouper: &Option<Box<dyn Fn(&Value) -> Result<String, ShellError> + Send>>,
tag: impl Into<Tag>,
) -> Result<Value, ShellError> {
let tag = tag.into();
let mut groups: IndexMap<String, Vec<Value>> = IndexMap::new();
for value in values {
let group_key = if let Some(ref column_name) = column_name {
get_data_by_key(&value, column_name.borrow_spanned())
for value in values.table_entries() {
let group_key = if let Some(ref grouper) = grouper {
grouper(&value)
} else {
Some(value.clone())
as_string(&value)
};
if let Some(group_key) = group_key {
let group_key = if let Some(ref grouper) = grouper {
grouper(&group_key)
} else {
as_string(&group_key)
};
let group = groups.entry(group_key?).or_insert(vec![]);
group.push((*value).clone());
} else {
let column_name = column_name.unwrap_or_else(|| String::from("").tagged(&tag));
let possibilities = value.data_descriptors();
let mut possible_matches: Vec<_> = possibilities
.iter()
.map(|x| (natural::distance::levenshtein_distance(x, &column_name), x))
.collect();
possible_matches.sort();
if !possible_matches.is_empty() {
return Err(ShellError::labeled_error(
"Unknown column",
format!("did you mean '{}'?", possible_matches[0].1),
column_name.tag(),
));
} else {
return Err(ShellError::labeled_error(
"Unknown column",
"row does not contain this column",
column_name.tag(),
));
}
}
let group = groups.entry(group_key?).or_insert(vec![]);
group.push((*value).clone());
}
let mut out = TaggedDictBuilder::new(&tag);

View File

@ -1,3 +1,5 @@
pub mod group;
pub mod split;
pub use crate::utils::data::group::group;
pub use crate::utils::data::split::split;

View File

@ -0,0 +1,53 @@
use nu_errors::ShellError;
use nu_protocol::{SpannedTypeName, TaggedDictBuilder, UntaggedValue, Value};
use nu_source::Tag;
use crate::utils::data::group;
#[allow(clippy::type_complexity)]
pub fn split(
value: &Value,
splitter: &Option<Box<dyn Fn(&Value) -> Result<String, ShellError> + Send>>,
tag: impl Into<Tag>,
) -> Result<Value, ShellError> {
let tag = tag.into();
let mut splits = indexmap::IndexMap::new();
for (column, value) in value.row_entries() {
if !&value.is_table() {
return Err(ShellError::type_error(
"a table value",
value.spanned_type_name(),
));
}
match group(&value, splitter, &tag) {
Ok(grouped) => {
for (split_label, subset) in grouped.row_entries() {
let s = splits
.entry(split_label.clone())
.or_insert(indexmap::IndexMap::new());
if !&subset.is_table() {
return Err(ShellError::type_error(
"a table value",
subset.spanned_type_name(),
));
}
s.insert(column.clone(), subset.clone());
}
}
Err(err) => return Err(err),
}
}
let mut out = TaggedDictBuilder::new(&tag);
for (k, v) in splits.into_iter() {
out.insert_untagged(k, UntaggedValue::row(v));
}
Ok(out.into_value())
}

View File

@ -12,7 +12,7 @@ use num_traits::Zero;
const ERR_EMPTY_DATA: &str = "Cannot perform aggregate math operation on empty data";
pub fn columns_sorted(
_group_by_name: Option<String>,
_group_by_name: Option<Tagged<String>>,
value: &Value,
tag: impl Into<Tag>,
) -> Vec<Tagged<String>> {
@ -61,7 +61,7 @@ pub fn columns_sorted(
}
pub fn t_sort(
group_by_name: Option<String>,
group_by_name: Option<Tagged<String>>,
split_by_name: Option<String>,
value: &Value,
tag: impl Into<Tag>,
@ -288,14 +288,14 @@ pub fn reducer_for(
command: Reduce,
) -> Box<dyn Fn(Value, Vec<Value>) -> Result<Value, ShellError> + Send + Sync + 'static> {
match command {
Reduce::Sum | Reduce::Default => Box::new(formula(Value::zero(), Box::new(sum))),
Reduce::Summation | Reduce::Default => Box::new(formula(Value::zero(), Box::new(sum))),
Reduce::Minimum => Box::new(|_, values| min(values)),
Reduce::Maximum => Box::new(|_, values| max(values)),
}
}
pub enum Reduce {
Sum,
Summation,
Minimum,
Maximum,
Default,
@ -309,7 +309,7 @@ pub fn reduce(
let tag = tag.into();
let reduce_with = match reducer {
Some(cmd) if cmd == "sum" => reducer_for(Reduce::Sum),
Some(cmd) if cmd == "sum" => reducer_for(Reduce::Summation),
Some(cmd) if cmd == "min" => reducer_for(Reduce::Minimum),
Some(cmd) if cmd == "max" => reducer_for(Reduce::Maximum),
Some(_) | None => reducer_for(Reduce::Default),
@ -454,12 +454,13 @@ mod tests {
}
fn nu_releases_grouped_by_date() -> Result<Value, ShellError> {
let key = String::from("date").tagged_unknown();
group(&key, nu_releases_commiters(), Tag::unknown())
let key = Some(String::from("date").tagged_unknown());
let sample = table(&nu_releases_committers());
group(&key, &sample, Tag::unknown())
}
fn nu_releases_sorted_by_date() -> Result<Value, ShellError> {
let key = String::from("date");
let key = String::from("date").tagged(Tag::unknown());
t_sort(
Some(key),
@ -481,7 +482,7 @@ mod tests {
)
}
fn nu_releases_commiters() -> Vec<Value> {
fn nu_releases_committers() -> Vec<Value> {
vec![
row(
indexmap! {"name".into() => string("AR"), "country".into() => string("EC"), "date".into() => string("August 23-2019")},
@ -515,7 +516,7 @@ mod tests {
#[test]
fn show_columns_sorted_given_a_column_to_sort_by() -> Result<(), ShellError> {
let by_column = String::from("date");
let by_column = String::from("date").tagged(Tag::unknown());
assert_eq!(
columns_sorted(
@ -535,7 +536,7 @@ mod tests {
#[test]
fn sorts_the_tables() -> Result<(), ShellError> {
let group_by = String::from("date");
let group_by = String::from("date").tagged(Tag::unknown());
assert_eq!(
t_sort(
@ -641,7 +642,7 @@ mod tests {
fn reducer_computes_given_a_sum_command() -> Result<(), ShellError> {
let subject = vec![int(1), int(1), int(1)];
let action = reducer_for(Reduce::Sum);
let action = reducer_for(Reduce::Summation);
assert_eq!(action(Value::zero(), subject)?, int(3));

View File

@ -52,6 +52,20 @@ fn cal_rows_in_2020() {
assert!(actual.out.contains("62"));
}
#[test]
fn cal_week_day_start_monday() {
let actual = nu!(
cwd: ".", pipeline(
r#"
cal --full-year 2020 -m --month-names --week-start monday | where month == january | to json
"#
));
let cal_january_json = r#"[{"month":"january","monday":null,"tuesday":null,"wednesday":1,"thursday":2,"friday":3,"saturday":4,"sunday":5},{"month":"january","monday":6,"tuesday":7,"wednesday":8,"thursday":9,"friday":10,"saturday":11,"sunday":12},{"month":"january","monday":13,"tuesday":14,"wednesday":15,"thursday":16,"friday":17,"saturday":18,"sunday":19},{"month":"january","monday":20,"tuesday":21,"wednesday":22,"thursday":23,"friday":24,"saturday":25,"sunday":26},{"month":"january","monday":27,"tuesday":28,"wednesday":29,"thursday":30,"friday":31,"saturday":null,"sunday":null}]"#;
assert_eq!(actual.out, cal_january_json);
}
#[test]
fn cal_sees_pipeline_year() {
let actual = nu!(

View File

@ -432,3 +432,27 @@ fn valuesystem_path_not_found() {
assert!(actual.err.contains("No such path exists"));
})
}
#[cfg(target_os = "windows")]
#[test]
fn test_change_windows_drive() {
Playground::setup("cd_test_20", |dirs, sandbox| {
sandbox.mkdir("test_folder");
let _actual = nu!(
cwd: dirs.test(),
r#"
subst Z: test_folder
Z:
echo "some text" | save test_file.txt
cd ~
subst Z: /d
"#
);
assert!(dirs
.test()
.join("test_folder")
.join("test_file.txt")
.exists());
})
}

View File

@ -4,7 +4,7 @@ use nu_test_support::nu;
fn drop_rows() {
let actual = nu!(
cwd: "tests/fixtures/formats",
r#"echo '[{"foo": 3}, {"foo": 8}, {"foo": 4}]' | from json | drop 2 | get foo | sum | echo $it"#
r#"echo '[{"foo": 3}, {"foo": 8}, {"foo": 4}]' | from json | drop 2 | get foo | math sum | echo $it"#
);
assert_eq!(actual.out, "3");

View File

@ -5,7 +5,7 @@ fn each_works_separately() {
let actual = nu!(
cwd: "tests/fixtures/formats", pipeline(
r#"
echo [1 2 3] | each { echo $it 10 | sum } | to json | echo $it
echo [1 2 3] | each { echo $it 10 | math sum } | to json | echo $it
"#
));

View File

@ -0,0 +1,245 @@
use nu_test_support::fs::Stub::EmptyFile;
use nu_test_support::playground::Playground;
use nu_test_support::{nu, pipeline};
#[test]
fn gets_all_rows_by_every_zero() {
Playground::setup("every_test_1", |dirs, sandbox| {
sandbox.with_files(vec![
EmptyFile("los.txt"),
EmptyFile("tres.txt"),
EmptyFile("amigos.txt"),
EmptyFile("arepas.clu"),
]);
let actual = nu!(
cwd: dirs.test(), pipeline(
r#"
ls
| get name
| every 0
"#
));
let expected = nu!(
cwd: dirs.test(), pipeline(
r#"
echo [ amigos.txt arepas.clu los.txt tres.txt ]
"#
));
assert_eq!(actual.out, expected.out);
})
}
#[test]
fn gets_no_rows_by_every_skip_zero() {
Playground::setup("every_test_2", |dirs, sandbox| {
sandbox.with_files(vec![
EmptyFile("los.txt"),
EmptyFile("tres.txt"),
EmptyFile("amigos.txt"),
EmptyFile("arepas.clu"),
]);
let actual = nu!(
cwd: dirs.test(), pipeline(
r#"
ls
| get name
| every 0 --skip
"#
));
let expected = nu!(
cwd: dirs.test(), pipeline(
r#"
echo [ ]
"#
));
assert_eq!(actual.out, expected.out);
})
}
#[test]
fn gets_all_rows_by_every_one() {
Playground::setup("every_test_3", |dirs, sandbox| {
sandbox.with_files(vec![
EmptyFile("los.txt"),
EmptyFile("tres.txt"),
EmptyFile("amigos.txt"),
EmptyFile("arepas.clu"),
]);
let actual = nu!(
cwd: dirs.test(), pipeline(
r#"
ls
| get name
| every 1
"#
));
let expected = nu!(
cwd: dirs.test(), pipeline(
r#"
echo [ amigos.txt arepas.clu los.txt tres.txt ]
"#
));
assert_eq!(actual.out, expected.out);
})
}
#[test]
fn gets_no_rows_by_every_skip_one() {
Playground::setup("every_test_4", |dirs, sandbox| {
sandbox.with_files(vec![
EmptyFile("los.txt"),
EmptyFile("tres.txt"),
EmptyFile("amigos.txt"),
EmptyFile("arepas.clu"),
]);
let actual = nu!(
cwd: dirs.test(), pipeline(
r#"
ls
| get name
| every 1 --skip
"#
));
let expected = nu!(
cwd: dirs.test(), pipeline(
r#"
echo [ ]
"#
));
assert_eq!(actual.out, expected.out);
})
}
#[test]
fn gets_first_row_by_every_too_much() {
Playground::setup("every_test_5", |dirs, sandbox| {
sandbox.with_files(vec![
EmptyFile("los.txt"),
EmptyFile("tres.txt"),
EmptyFile("amigos.txt"),
EmptyFile("arepas.clu"),
]);
let actual = nu!(
cwd: dirs.test(), pipeline(
r#"
ls
| get name
| every 999
"#
));
let expected = nu!(
cwd: dirs.test(), pipeline(
r#"
echo [ amigos.txt ]
"#
));
assert_eq!(actual.out, expected.out);
})
}
#[test]
fn gets_all_rows_except_first_by_every_skip_too_much() {
Playground::setup("every_test_6", |dirs, sandbox| {
sandbox.with_files(vec![
EmptyFile("los.txt"),
EmptyFile("tres.txt"),
EmptyFile("amigos.txt"),
EmptyFile("arepas.clu"),
]);
let actual = nu!(
cwd: dirs.test(), pipeline(
r#"
ls
| get name
| every 999 --skip
"#
));
let expected = nu!(
cwd: dirs.test(), pipeline(
r#"
echo [ arepas.clu los.txt tres.txt ]
"#
));
assert_eq!(actual.out, expected.out);
})
}
#[test]
fn gets_every_third_row() {
Playground::setup("every_test_7", |dirs, sandbox| {
sandbox.with_files(vec![
EmptyFile("los.txt"),
EmptyFile("tres.txt"),
EmptyFile("quatro.txt"),
EmptyFile("amigos.txt"),
EmptyFile("arepas.clu"),
]);
let actual = nu!(
cwd: dirs.test(), pipeline(
r#"
ls
| get name
| every 3
"#
));
let expected = nu!(
cwd: dirs.test(), pipeline(
r#"
echo [ amigos.txt quatro.txt ]
"#
));
assert_eq!(actual.out, expected.out);
})
}
#[test]
fn skips_every_third_row() {
Playground::setup("every_test_8", |dirs, sandbox| {
sandbox.with_files(vec![
EmptyFile("los.txt"),
EmptyFile("tres.txt"),
EmptyFile("quatro.txt"),
EmptyFile("amigos.txt"),
EmptyFile("arepas.clu"),
]);
let actual = nu!(
cwd: dirs.test(), pipeline(
r#"
ls
| get name
| every 3 --skip
"#
));
let expected = nu!(
cwd: dirs.test(), pipeline(
r#"
echo [ arepas.clu los.txt tres.txt ]
"#
));
assert_eq!(actual.out, expected.out);
})
}

View File

@ -22,7 +22,7 @@ fn adds_value_provided_if_column_is_empty() {
open likes.csv
| empty? likes 1
| get likes
| sum
| math sum
| echo $it
"#
));
@ -43,7 +43,7 @@ fn adds_value_provided_for_columns_that_are_empty() {
{"boost": 1, "check": {}},
{"boost": null, "check": ["" {} [] ""]}
]
"#,
)]);
@ -53,7 +53,7 @@ fn adds_value_provided_for_columns_that_are_empty() {
open checks.json
| empty? boost check 1
| get boost check
| sum
| math sum
| echo $it
"#
));

View File

@ -22,7 +22,7 @@ fn rows() {
open caballeros.csv
| keep 3
| get lucky_code
| sum
| math sum
| echo $it
"#
));

View File

@ -41,7 +41,7 @@ fn condition_is_met() {
| keep-until "Chicken Collection" == "Red Chickens"
| str to-int "31/04/2020"
| get "31/04/2020"
| sum
| math sum
| echo $it
"#
));

View File

@ -41,7 +41,7 @@ fn condition_is_met() {
| keep-while "Chicken Collection" != "Blue Chickens"
| str to-int "31/04/2020"
| get "31/04/2020"
| sum
| math sum
| echo $it
"#
));

View File

@ -7,11 +7,11 @@ fn can_average_numbers() {
r#"
open sgml_description.json
| get glossary.GlossDiv.GlossList.GlossEntry.Sections
| math average
| math avg
| echo $it
"#
));
println!("{:?}", actual.err);
assert_eq!(actual.out, "101.5")
}
@ -19,7 +19,7 @@ fn can_average_numbers() {
fn can_average_bytes() {
let actual = nu!(
cwd: "tests/fixtures/formats",
"ls | sort-by name | skip 1 | first 2 | get size | math average | format \"{$it}\" | echo $it"
"ls | sort-by name | skip 1 | first 2 | get size | math avg | format \"{$it}\" | echo $it"
);
assert_eq!(actual.out, "1.6 KB");

View File

@ -0,0 +1,43 @@
use nu_test_support::{nu, pipeline};
#[test]
fn median_numbers_with_even_rows() {
let actual = nu!(
cwd: ".", pipeline(
r#"
echo [10 6 19 21 4]
| math median
| echo $it
"#
));
assert_eq!(actual.out, "10")
}
#[test]
fn median_numbers_with_odd_rows() {
let actual = nu!(
cwd: ".", pipeline(
r#"
echo [3 8 9 12 12 15]
| math median
| echo $it
"#
));
assert_eq!(actual.out, "10.5")
}
#[test]
fn median_mixed_numbers() {
let actual = nu!(
cwd: ".", pipeline(
r#"
echo [-11.5 -13.5 10]
| math median
| echo $it
"#
));
assert_eq!(actual.out, "-11.5")
}

View File

@ -1,3 +1,6 @@
mod avg;
mod median;
use nu_test_support::{nu, pipeline};
#[test]
@ -84,6 +87,54 @@ fn division_of_ints2() {
assert_eq!(actual.out, "0.25");
}
#[test]
fn error_zero_division_int_int() {
let actual = nu!(
cwd: "tests/fixtures/formats", pipeline(
r#"
= 1 / 0
"#
));
assert!(actual.err.contains("division by zero"));
}
#[test]
fn error_zero_division_decimal_int() {
let actual = nu!(
cwd: "tests/fixtures/formats", pipeline(
r#"
= 1.0 / 0
"#
));
assert!(actual.err.contains("division by zero"));
}
#[test]
fn error_zero_division_int_decimal() {
let actual = nu!(
cwd: "tests/fixtures/formats", pipeline(
r#"
= 1 / 0.0
"#
));
assert!(actual.err.contains("division by zero"));
}
#[test]
fn error_zero_division_decimal_decimal() {
let actual = nu!(
cwd: "tests/fixtures/formats", pipeline(
r#"
= 1.0 / 0.0
"#
));
assert!(actual.err.contains("division by zero"));
}
#[test]
fn proper_precedence_history() {
let actual = nu!(

View File

@ -25,7 +25,7 @@ fn all() {
open meals.json
| get meals
| get calories
| sum
| math sum
| echo $it
"#
));
@ -53,7 +53,7 @@ fn outputs_zero_with_no_input() {
let actual = nu!(
cwd: dirs.test(), pipeline(
r#"
sum
math sum
| echo $it
"#
));
@ -74,7 +74,7 @@ fn compute_sum_of_individual_row() -> Result<(), String> {
for (column_name, expected_value) in answers_for_columns.iter() {
let actual = nu!(
cwd: "tests/fixtures/formats/",
format!("open sample-ps-output.json | select {} | sum | get {}", column_name, column_name)
format!("open sample-ps-output.json | select {} | math sum | get {}", column_name, column_name)
);
let result =
f64::from_str(&actual.out).map_err(|_| String::from("Failed to parse float."))?;
@ -95,7 +95,7 @@ fn compute_sum_of_table() -> Result<(), String> {
for (column_name, expected_value) in answers_for_columns.iter() {
let actual = nu!(
cwd: "tests/fixtures/formats/",
format!("open sample-ps-output.json | select cpu mem virtual | sum | get {}", column_name)
format!("open sample-ps-output.json | select cpu mem virtual | math sum | get {}", column_name)
);
let result =
f64::from_str(&actual.out).map_err(|_| String::from("Failed to parse float."))?;
@ -108,7 +108,7 @@ fn compute_sum_of_table() -> Result<(), String> {
fn sum_of_a_row_containing_a_table_is_an_error() {
let actual = nu!(
cwd: "tests/fixtures/formats/",
"open sample-sys-output.json | sum"
"open sample-sys-output.json | math sum"
);
assert!(actual
.err

View File

@ -33,7 +33,7 @@ fn row() {
| merge { open new_caballeros.csv }
| where country in: ["Guayaquil Ecuador" "New Zealand"]
| get luck
| sum
| math sum
| echo $it
"#
));

View File

@ -13,6 +13,7 @@ mod default;
mod drop;
mod each;
mod enter;
mod every;
mod first;
mod format;
mod get;
@ -47,7 +48,6 @@ mod split_by;
mod split_column;
mod split_row;
mod str_;
mod sum;
mod touch;
mod trim;
mod uniq;

View File

@ -12,20 +12,20 @@ fn condition_is_met() {
--------------------------------------------------------------------
Chicken Collection,29/04/2020,30/04/2020,31/04/2020,
Yellow Chickens,,,
Andrés,1,1,1
Jonathan,1,1,1
Jason,1,1,1
Yehuda,1,1,1
Andrés,0,0,1
Jonathan,0,0,1
Jason,0,0,1
Yehuda,0,0,1
Blue Chickens,,,
Andrés,1,1,2
Jonathan,1,1,2
Jason,1,1,2
Yehuda,1,1,2
Andrés,0,0,1
Jonathan,0,0,1
Jason,0,0,1
Yehuda,0,0,2
Red Chickens,,,
Andrés,1,1,3
Jonathan,1,1,3
Jason,1,1,3
Yehuda,1,1,3
Andrés,0,0,1
Jonathan,0,0,1
Jason,0,0,1
Yehuda,0,0,3
"#,
)]);
@ -40,11 +40,11 @@ fn condition_is_met() {
| skip-until "Chicken Collection" == "Red Chickens"
| str to-int "31/04/2020"
| get "31/04/2020"
| sum
| math sum
| echo $it
"#
));
assert_eq!(actual.out, "12");
assert_eq!(actual.out, "6");
})
}

View File

@ -108,7 +108,7 @@ fn converts_to_decimal() {
echo "3.1, 0.0415"
| split row ","
| str to-decimal
| sum
| math sum
"#
));
@ -130,7 +130,7 @@ fn sets() {
cwd: dirs.test(), pipeline(
r#"
open sample.toml
| str set wykittenshell package.name
| str set wykittenshell package.name
| get package.name
| echo $it
"#

View File

@ -140,3 +140,26 @@ fn uniq_when_keys_out_of_order() {
assert_eq!(actual.out, "1");
}
#[test]
fn uniq_counting() {
let actual = nu!(
cwd: "tests/fixtures/formats", pipeline(
r#"
echo '["A", "B", "A"]'
| from json
| wrap item
| uniq --count
"#
));
let expected = nu!(
cwd: "tests/fixtures/formats", pipeline(
r#"
echo '[{"item": "A", "count": 2}, {"item": "B", "count": 1}]'
| from json
"#
));
print!("{}", actual.out);
print!("{}", expected.out);
assert_eq!(actual.out, expected.out);
}

View File

@ -14,7 +14,7 @@ fn filters_by_unit_size_comparison() {
fn filters_with_nothing_comparison() {
let actual = nu!(
cwd: "tests/fixtures/formats",
r#"echo '[{"foo": 3}, {"foo": null}, {"foo": 4}]' | from json | get foo | compact | where $it > 1 | sum | echo $it"#
r#"echo '[{"foo": 3}, {"foo": null}, {"foo": 4}]' | from json | get foo | compact | where $it > 1 | math sum | echo $it"#
);
assert_eq!(actual.out, "7");
@ -24,7 +24,7 @@ fn filters_with_nothing_comparison() {
fn where_in_table() {
let actual = nu!(
cwd: "tests/fixtures/formats",
r#"echo '[{"name": "foo", "size": 3}, {"name": "foo", "size": 2}, {"name": "bar", "size": 4}]' | from json | where name in: ["foo"] | get size | sum | echo $it"#
r#"echo '[{"name": "foo", "size": 3}, {"name": "foo", "size": 2}, {"name": "bar", "size": 4}]' | from json | where name in: ["foo"] | get size | math sum | echo $it"#
);
assert_eq!(actual.out, "5");
@ -34,7 +34,7 @@ fn where_in_table() {
fn where_not_in_table() {
let actual = nu!(
cwd: "tests/fixtures/formats",
r#"echo '[{"name": "foo", "size": 3}, {"name": "foo", "size": 2}, {"name": "bar", "size": 4}]' | from json | where name not-in: ["foo"] | get size | sum | echo $it"#
r#"echo '[{"name": "foo", "size": 3}, {"name": "foo", "size": 2}, {"name": "bar", "size": 4}]' | from json | where name not-in: ["foo"] | get size | math sum | echo $it"#
);
assert_eq!(actual.out, "4");

View File

@ -215,4 +215,13 @@ pub mod value {
))
.into_untagged_value())
}
#[macro_export]
macro_rules! row {
($( $key: expr => $val: expr ),*) => {{
let mut map = indexmap::IndexMap::new();
$( map.insert($key, $val); )*
UntaggedValue::row(map).into_untagged_value()
}}
}
}

View File

@ -91,6 +91,14 @@ impl UntaggedValue {
}
}
/// Returns true if this value represents a table
pub fn is_table(&self) -> bool {
match self {
UntaggedValue::Table(_) => true,
_ => false,
}
}
/// Returns true if the value represents something other than Nothing
pub fn is_some(&self) -> bool {
!self.is_none()

View File

@ -0,0 +1,16 @@
[package]
name = "nu-table"
version = "0.15.1"
authors = ["The Nu Project Contributors"]
edition = "2018"
description = "Nushell table printing"
license = "MIT"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[[bin]]
name = "table"
path = "src/main.rs"
[dependencies]
unicode-width = "0.1.7"
ansi_term = "0.12.1"

View File

@ -0,0 +1,5 @@
mod table;
mod wrap;
pub use table::{draw_table, StyledString, Table, TextStyle, Theme};
pub use wrap::Alignment;

View File

@ -0,0 +1,27 @@
use nu_table::{draw_table, StyledString, Table, TextStyle, Theme};
fn main() {
let args: Vec<_> = std::env::args().collect();
let width = args[1].parse::<usize>().expect("Need a width in columns");
let msg = args[2..]
.iter()
.map(|x| StyledString::new(x.to_owned(), TextStyle::basic()))
.collect();
let t = Table::new(
vec![
StyledString::new("Test me".to_owned(), TextStyle::default_header()),
StyledString::new(
"Long column \n name with carriage returns and a lot of text\n check it out"
.to_owned(),
TextStyle::default_header(),
),
StyledString::new("Another".to_owned(), TextStyle::default_header()),
],
vec![msg; 2],
Theme::compact(),
);
draw_table(&t, width);
}

View File

@ -0,0 +1,710 @@
use crate::wrap::{column_width, split_sublines, wrap, Alignment, Subline, WrappedCell};
enum SeparatorPosition {
Top,
Middle,
Bottom,
}
#[derive(Debug)]
pub struct Table {
pub headers: Vec<StyledString>,
pub data: Vec<Vec<StyledString>>,
pub theme: Theme,
}
#[derive(Debug, Clone)]
pub struct StyledString {
pub contents: String,
pub style: TextStyle,
}
impl StyledString {
pub fn new(contents: String, style: TextStyle) -> StyledString {
StyledString { contents, style }
}
}
#[derive(Debug, Clone)]
pub struct TextStyle {
pub is_bold: bool,
pub alignment: Alignment,
pub color: Option<ansi_term::Colour>,
}
impl TextStyle {
pub fn basic() -> TextStyle {
TextStyle {
is_bold: false,
alignment: Alignment::Left,
color: None,
}
}
pub fn basic_right() -> TextStyle {
TextStyle {
is_bold: false,
alignment: Alignment::Right,
color: None,
}
}
pub fn default_header() -> TextStyle {
TextStyle {
is_bold: true,
alignment: Alignment::Center,
color: Some(ansi_term::Colour::Green),
}
}
}
#[derive(Debug, Clone)]
pub struct Theme {
pub top_left: char,
pub middle_left: char,
pub bottom_left: char,
pub top_center: char,
pub center: char,
pub bottom_center: char,
pub top_right: char,
pub middle_right: char,
pub bottom_right: char,
pub top_horizontal: char,
pub middle_horizontal: char,
pub bottom_horizontal: char,
pub left_vertical: char,
pub center_vertical: char,
pub right_vertical: char,
pub separate_header: bool,
pub separate_rows: bool,
pub print_left_border: bool,
pub print_right_border: bool,
pub print_top_border: bool,
pub print_bottom_border: bool,
}
impl Theme {
#[allow(unused)]
pub fn basic() -> Theme {
Theme {
top_left: '+',
middle_left: '+',
bottom_left: '+',
top_center: '+',
center: '+',
bottom_center: '+',
top_right: '+',
middle_right: '+',
bottom_right: '+',
top_horizontal: '-',
middle_horizontal: '-',
bottom_horizontal: '-',
left_vertical: '|',
center_vertical: '|',
right_vertical: '|',
separate_header: true,
separate_rows: true,
print_left_border: true,
print_right_border: true,
print_top_border: true,
print_bottom_border: true,
}
}
#[allow(unused)]
pub fn thin() -> Theme {
Theme {
top_left: '',
middle_left: '',
bottom_left: '',
top_center: '',
center: '',
bottom_center: '',
top_right: '',
middle_right: '',
bottom_right: '',
top_horizontal: '',
middle_horizontal: '',
bottom_horizontal: '',
left_vertical: '',
center_vertical: '',
right_vertical: '',
separate_header: true,
separate_rows: true,
print_left_border: true,
print_right_border: true,
print_top_border: true,
print_bottom_border: true,
}
}
#[allow(unused)]
pub fn light() -> Theme {
Theme {
top_left: ' ',
middle_left: '',
bottom_left: ' ',
top_center: ' ',
center: '',
bottom_center: ' ',
top_right: ' ',
middle_right: '',
bottom_right: ' ',
top_horizontal: ' ',
middle_horizontal: '',
bottom_horizontal: ' ',
left_vertical: ' ',
center_vertical: ' ',
right_vertical: ' ',
separate_header: true,
separate_rows: false,
print_left_border: true,
print_right_border: true,
print_top_border: false,
print_bottom_border: true,
}
}
#[allow(unused)]
pub fn compact() -> Theme {
Theme {
top_left: '',
middle_left: '',
bottom_left: '',
top_center: '',
center: '',
bottom_center: '',
top_right: '',
middle_right: '',
bottom_right: '',
top_horizontal: '',
middle_horizontal: '',
bottom_horizontal: '',
left_vertical: ' ',
center_vertical: '',
right_vertical: ' ',
separate_header: true,
separate_rows: false,
print_left_border: false,
print_right_border: false,
print_top_border: true,
print_bottom_border: true,
}
}
}
impl Table {
pub fn new(headers: Vec<StyledString>, data: Vec<Vec<StyledString>>, theme: Theme) -> Table {
Table {
headers,
data,
theme,
}
}
}
#[derive(Debug)]
pub struct ProcessedTable<'a> {
pub headers: Vec<ProcessedCell<'a>>,
pub data: Vec<Vec<ProcessedCell<'a>>>,
pub theme: Theme,
}
#[derive(Debug)]
pub struct ProcessedCell<'a> {
pub contents: Vec<Vec<Subline<'a>>>,
pub style: TextStyle,
}
#[derive(Debug)]
pub struct WrappedTable {
pub column_widths: Vec<usize>,
pub headers: Vec<WrappedCell>,
pub data: Vec<Vec<WrappedCell>>,
pub theme: Theme,
}
impl WrappedTable {
fn print_separator(&self, separator_position: SeparatorPosition) {
let column_count = self.column_widths.len();
let mut output = String::new();
match separator_position {
SeparatorPosition::Top => {
for column in self.column_widths.iter().enumerate() {
if column.0 == 0 && self.theme.print_left_border {
output.push(self.theme.top_left);
}
for _ in 0..*column.1 {
output.push(self.theme.top_horizontal);
}
output.push(self.theme.top_horizontal);
output.push(self.theme.top_horizontal);
if column.0 == column_count - 1 {
if self.theme.print_right_border {
output.push(self.theme.top_right);
}
} else {
output.push(self.theme.top_center);
}
}
}
SeparatorPosition::Middle => {
for column in self.column_widths.iter().enumerate() {
if column.0 == 0 && self.theme.print_left_border {
output.push(self.theme.middle_left);
}
for _ in 0..*column.1 {
output.push(self.theme.middle_horizontal);
}
output.push(self.theme.middle_horizontal);
output.push(self.theme.middle_horizontal);
if column.0 == column_count - 1 {
if self.theme.print_right_border {
output.push(self.theme.middle_right);
}
} else {
output.push(self.theme.center);
}
}
}
SeparatorPosition::Bottom => {
for column in self.column_widths.iter().enumerate() {
if column.0 == 0 && self.theme.print_left_border {
output.push(self.theme.bottom_left);
}
for _ in 0..*column.1 {
output.push(self.theme.bottom_horizontal);
}
output.push(self.theme.bottom_horizontal);
output.push(self.theme.bottom_horizontal);
if column.0 == column_count - 1 {
if self.theme.print_right_border {
output.push(self.theme.bottom_right);
}
} else {
output.push(self.theme.bottom_center);
}
}
}
}
println!("{}", output);
}
fn print_cell_contents(&self, cells: &[WrappedCell]) {
for current_line in 0.. {
let mut lines_printed = 0;
let mut output = if self.theme.print_left_border {
self.theme.left_vertical.to_string()
} else {
String::new()
};
for column in cells.iter().enumerate() {
if let Some(line) = (column.1).lines.get(current_line) {
let remainder = self.column_widths[column.0] - line.width;
output.push(' ');
match column.1.style.alignment {
Alignment::Left => {
if let Some(color) = column.1.style.color {
let color = if column.1.style.is_bold {
color.bold()
} else {
color.normal()
};
output.push_str(&color.paint(&line.line).to_string());
} else {
output.push_str(&line.line);
}
for _ in 0..remainder {
output.push(' ');
}
}
Alignment::Center => {
for _ in 0..remainder / 2 {
output.push(' ');
}
if let Some(color) = column.1.style.color {
let color = if column.1.style.is_bold {
color.bold()
} else {
color.normal()
};
output.push_str(&color.paint(&line.line).to_string());
} else {
output.push_str(&line.line);
}
for _ in 0..(remainder / 2 + remainder % 2) {
output.push(' ');
}
}
Alignment::Right => {
for _ in 0..remainder {
output.push(' ');
}
if let Some(color) = column.1.style.color {
let color = if column.1.style.is_bold {
color.bold()
} else {
color.normal()
};
output.push_str(&color.paint(&line.line).to_string());
} else {
output.push_str(&line.line);
}
}
}
output.push(' ');
lines_printed += 1;
} else {
for _ in 0..self.column_widths[column.0] + 2 {
output.push(' ');
}
}
if column.0 < cells.len() - 1 {
output.push(self.theme.center_vertical);
} else if self.theme.print_right_border {
output.push(self.theme.right_vertical);
}
}
if lines_printed == 0 {
break;
} else {
println!("{}", output);
}
}
}
fn new_print_table(&self) {
if self.data.is_empty() {
return;
}
if self.theme.print_top_border {
self.print_separator(SeparatorPosition::Top);
}
if !self.headers.is_empty() {
self.print_cell_contents(&self.headers);
}
let mut first_row = true;
for row in &self.data {
if !first_row {
if self.theme.separate_rows {
self.print_separator(SeparatorPosition::Middle);
}
} else {
first_row = false;
if self.theme.separate_header && !self.headers.is_empty() {
self.print_separator(SeparatorPosition::Middle);
}
}
self.print_cell_contents(row);
}
if self.theme.print_bottom_border {
self.print_separator(SeparatorPosition::Bottom);
}
}
}
fn process_table(table: &Table) -> ProcessedTable {
let mut processed_data = vec![];
for row in &table.data {
let mut out_row = vec![];
for column in row {
out_row.push(ProcessedCell {
contents: split_sublines(&column.contents),
style: column.style.clone(),
});
}
processed_data.push(out_row);
}
let mut processed_headers = vec![];
for header in &table.headers {
processed_headers.push(ProcessedCell {
contents: split_sublines(&header.contents),
style: header.style.clone(),
});
}
ProcessedTable {
headers: processed_headers,
data: processed_data,
theme: table.theme.clone(),
}
}
fn get_max_column_widths(processed_table: &ProcessedTable) -> Vec<usize> {
use std::cmp::max;
let mut max_num_columns = 0;
max_num_columns = max(max_num_columns, processed_table.headers.len());
for row in &processed_table.data {
max_num_columns = max(max_num_columns, row.len());
}
let mut output = vec![0; max_num_columns];
for column in processed_table.headers.iter().enumerate() {
output[column.0] = max(output[column.0], column_width(&column.1.contents));
}
for row in &processed_table.data {
for column in row.iter().enumerate() {
output[column.0] = max(output[column.0], column_width(&column.1.contents));
}
}
output
}
pub fn draw_table(table: &Table, termwidth: usize) {
// Remove the edges, if used
let termwidth = if table.theme.print_left_border && table.theme.print_right_border {
termwidth - 2
} else if table.theme.print_left_border || table.theme.print_right_border {
termwidth - 1
} else {
termwidth
};
let processed_table = process_table(table);
let max_per_column = get_max_column_widths(&processed_table);
// maybe_truncate_columns(&mut headers, &mut entries, termwidth);
let headers_len = table.headers.len();
// fix the length of the table if there are no headers:
let headers_len = if headers_len == 0 {
if !table.data.is_empty() && !table.data[0].is_empty() {
table.data[0].len()
} else {
return;
}
} else {
headers_len
};
// Measure how big our columns need to be (accounting for separators also)
let max_naive_column_width = (termwidth - 3 * (headers_len - 1)) / headers_len;
let column_space = ColumnSpace::measure(&max_per_column, max_naive_column_width, headers_len);
// This gives us the max column width
let max_column_width = column_space.max_width(termwidth);
// This width isn't quite right, as we're rounding off some of our space
let column_space = column_space.fix_almost_column_width(
&max_per_column,
max_naive_column_width,
max_column_width,
headers_len,
);
// This should give us the final max column width
let max_column_width = column_space.max_width(termwidth);
let wrapped_table = wrap_cells(processed_table, max_column_width);
wrapped_table.new_print_table();
}
fn wrap_cells(processed_table: ProcessedTable, max_column_width: usize) -> WrappedTable {
let mut column_widths = vec![
0;
std::cmp::max(
processed_table.headers.len(),
if !processed_table.data.is_empty() {
processed_table.data[0].len()
} else {
0
}
)
];
let mut output_headers = vec![];
for header in processed_table.headers.into_iter().enumerate() {
let mut wrapped = WrappedCell {
lines: vec![],
max_width: 0,
style: header.1.style,
};
for contents in header.1.contents.into_iter() {
let (mut lines, inner_max_width) = wrap(max_column_width, contents.into_iter());
wrapped.lines.append(&mut lines);
if inner_max_width > wrapped.max_width {
wrapped.max_width = inner_max_width;
}
}
if column_widths[header.0] < wrapped.max_width {
column_widths[header.0] = wrapped.max_width;
}
output_headers.push(wrapped);
}
let mut output_data = vec![];
for row in processed_table.data.into_iter() {
let mut output_row = vec![];
for column in row.into_iter().enumerate() {
let mut wrapped = WrappedCell {
lines: vec![],
max_width: 0,
style: column.1.style,
};
for contents in column.1.contents.into_iter() {
let (mut lines, inner_max_width) = wrap(max_column_width, contents.into_iter());
wrapped.lines.append(&mut lines);
if inner_max_width > wrapped.max_width {
wrapped.max_width = inner_max_width;
}
}
if column_widths[column.0] < wrapped.max_width {
column_widths[column.0] = wrapped.max_width;
}
output_row.push(wrapped);
}
output_data.push(output_row);
}
WrappedTable {
column_widths,
headers: output_headers,
data: output_data,
theme: processed_table.theme,
}
}
struct ColumnSpace {
num_overages: usize,
underage_sum: usize,
overage_separator_sum: usize,
}
impl ColumnSpace {
/// Measure how much space we have once we subtract off the columns who are small enough
fn measure(
max_per_column: &[usize],
max_naive_column_width: usize,
headers_len: usize,
) -> ColumnSpace {
let mut num_overages = 0;
let mut underage_sum = 0;
let mut overage_separator_sum = 0;
let iter = max_per_column.iter().enumerate().take(headers_len);
for (i, &column_max) in iter {
if column_max > max_naive_column_width {
num_overages += 1;
if i != (headers_len - 1) {
overage_separator_sum += 3;
}
if i == 0 {
overage_separator_sum += 1;
}
} else {
underage_sum += column_max;
// if column isn't last, add 3 for its separator
if i != (headers_len - 1) {
underage_sum += 3;
}
if i == 0 {
underage_sum += 1;
}
}
}
ColumnSpace {
num_overages,
underage_sum,
overage_separator_sum,
}
}
fn fix_almost_column_width(
self,
max_per_column: &[usize],
max_naive_column_width: usize,
max_column_width: usize,
headers_len: usize,
) -> ColumnSpace {
let mut num_overages = 0;
let mut overage_separator_sum = 0;
let mut underage_sum = self.underage_sum;
let iter = max_per_column.iter().enumerate().take(headers_len);
for (i, &column_max) in iter {
if column_max > max_naive_column_width {
if column_max <= max_column_width {
underage_sum += column_max;
// if column isn't last, add 3 for its separator
if i != (headers_len - 1) {
underage_sum += 3;
}
if i == 0 {
underage_sum += 1;
}
} else {
// Column is still too large, so let's count it
num_overages += 1;
if i != (headers_len - 1) {
overage_separator_sum += 3;
}
if i == 0 {
overage_separator_sum += 1;
}
}
}
}
ColumnSpace {
num_overages,
underage_sum,
overage_separator_sum,
}
}
fn max_width(&self, termwidth: usize) -> usize {
let ColumnSpace {
num_overages,
underage_sum,
overage_separator_sum,
} = self;
if *num_overages > 0 {
(termwidth - 1 - *underage_sum - *overage_separator_sum) / *num_overages
} else {
99999
}
}
}

236
crates/nu-table/src/wrap.rs Normal file
View File

@ -0,0 +1,236 @@
use crate::table::TextStyle;
use std::{fmt::Display, iter::Iterator};
use unicode_width::UnicodeWidthStr;
#[derive(Debug, Clone)]
pub enum Alignment {
Left,
Center,
Right,
}
#[derive(Debug)]
pub struct Subline<'a> {
pub subline: &'a str,
pub width: usize,
}
#[derive(Debug)]
pub struct Line<'a> {
pub sublines: Vec<Subline<'a>>,
pub width: usize,
}
#[derive(Debug)]
pub struct WrappedLine {
pub line: String,
pub width: usize,
}
#[derive(Debug)]
pub struct WrappedCell {
pub lines: Vec<WrappedLine>,
pub max_width: usize,
pub style: TextStyle,
}
impl<'a> Display for Line<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut first = true;
for subline in &self.sublines {
if !first {
write!(f, " ")?;
} else {
first = false;
}
write!(f, "{}", subline.subline)?;
}
Ok(())
}
}
pub fn split_sublines(input: &str) -> Vec<Vec<Subline>> {
input
.split_terminator('\n')
.map(|line| {
line.split_terminator(' ')
.map(|x| Subline {
subline: x,
width: UnicodeWidthStr::width(x),
})
.collect::<Vec<_>>()
})
.collect::<Vec<_>>()
}
pub fn column_width<'a>(input: &[Vec<Subline<'a>>]) -> usize {
let mut max = 0;
for line in input {
let mut total = 0;
let mut first = true;
for inp in line {
if !first {
// Account for the space
total += 1;
} else {
first = false;
}
total += inp.width;
}
if total > max {
max = total;
}
}
max
}
fn split_word<'a>(cell_width: usize, word: &'a str) -> Vec<Subline<'a>> {
use unicode_width::UnicodeWidthChar;
let mut output = vec![];
let mut current_width = 0;
let mut start_index = 0;
let mut end_index;
for c in word.char_indices() {
if let Some(width) = c.1.width() {
end_index = c.0;
if current_width + width > cell_width {
output.push(Subline {
subline: &word[start_index..end_index],
width: current_width,
});
start_index = c.0;
current_width = width;
} else {
current_width += width;
}
}
}
if start_index != word.len() {
output.push(Subline {
subline: &word[start_index..],
width: current_width,
});
}
output
}
pub fn wrap<'a>(
cell_width: usize,
mut input: impl Iterator<Item = Subline<'a>>,
) -> (Vec<WrappedLine>, usize) {
let mut lines = vec![];
let mut current_line: Vec<Subline> = vec![];
let mut current_width = 0;
let mut first = true;
let mut max_width = 0;
loop {
// println!("{:?}", current_line);
match input.next() {
Some(item) => {
if !first {
current_width += 1;
} else {
first = false;
}
if item.width + current_width > cell_width {
// If this is a really long single word, we need to split the word
if current_line.len() == 1 && current_width > cell_width {
max_width = cell_width;
let sublines = split_word(cell_width, &current_line[0].subline);
for subline in sublines {
let width = subline.width;
lines.push(Line {
sublines: vec![subline],
width,
});
}
first = true;
current_width = item.width;
current_line = vec![item];
} else {
if !current_line.is_empty() {
lines.push(Line {
sublines: current_line,
width: current_width,
});
}
first = true;
current_width = item.width;
current_line = vec![item];
max_width = std::cmp::max(max_width, current_width);
}
} else {
current_width += item.width;
current_line.push(item);
}
}
None => {
if current_width > cell_width {
// We need to break up the last word
let sublines = split_word(cell_width, &current_line[0].subline);
for subline in sublines {
let width = subline.width;
lines.push(Line {
sublines: vec![subline],
width,
});
}
} else if current_width > 0 {
lines.push(Line {
sublines: current_line,
width: current_width,
});
}
break;
}
}
}
let mut current_max = 0;
let mut output = vec![];
for line in lines {
let mut current_line_width = 0;
let mut first = true;
let mut current_line = String::new();
for subline in line.sublines {
if !first {
current_line_width += 1 + subline.width;
current_line.push(' ');
current_line.push_str(subline.subline);
} else {
first = false;
current_line_width = subline.width;
current_line.push_str(subline.subline);
}
}
if current_line_width > current_max {
current_max = current_line_width;
}
output.push(WrappedLine {
line: current_line,
width: current_line_width,
});
}
(output, current_max)
}

View File

@ -36,14 +36,14 @@ mod tests {
| from-csv
| get rusty_luck
| str --to-int
| sum
| math sum
| echo "$it"
"#,
);
assert_eq!(
actual,
r#"open los_tres_amigos.txt | from-csv | get rusty_luck | str --to-int | sum | echo "$it""#
r#"open los_tres_amigos.txt | from-csv | get rusty_luck | str --to-int | math sum | echo "$it""#
);
}
}

View File

@ -19,7 +19,7 @@ futures = { version = "0.3", features = ["compat", "io-compat"] }
futures-timer = "3.0.2"
[dependencies.heim]
version = "0.1.0-beta.2"
version = "0.1.0-beta.3"
default-features = false
features = ["process"]

View File

@ -20,7 +20,7 @@ battery = "0.7.5"
futures-util = "0.3.5"
[dependencies.heim]
version = "0.1.0-beta.2"
version = "0.1.0-beta.3"
default-features = false
features = ["host", "cpu", "memory", "disk", "net", "sensors"]

View File

@ -14,11 +14,14 @@ nu-plugin = { path = "../nu-plugin", version = "0.15.1" }
nu-protocol = { path = "../nu-protocol", version = "0.15.1" }
nu-source = { path = "../nu-source", version = "0.15.1" }
nu-errors = { path = "../nu-errors", version = "0.15.1" }
nu-cli = { path = "../nu-cli", version = "0.15.1" }
crossterm = "0.17.5"
syntect = { version = "4.2", default-features = false, features = ["default-fancy"]}
ansi_term = "0.12.1"
url = "2.1.1"
bat = "0.15.4"
textwrap = {version = "0.11.0", features = ["term_size"]}
[build-dependencies]
nu-build = { version = "0.15.1", path = "../nu-build" }

View File

@ -1,23 +1,7 @@
use crossterm::{
event::{KeyCode, KeyEvent},
ExecutableCommand,
};
use nu_protocol::{Primitive, UntaggedValue, Value};
use nu_source::AnchorLocation;
use syntect::easy::HighlightLines;
use syntect::highlighting::{Style, ThemeSet};
use syntect::parsing::SyntaxSet;
use std::io::Write;
use nu_source::{AnchorLocation, Tag};
use std::path::Path;
enum DrawCommand {
DrawString(Style, String),
NextLine,
}
#[derive(Default)]
pub struct TextView;
@ -27,209 +11,139 @@ impl TextView {
}
}
fn paint_textview(
draw_commands: &[DrawCommand],
starting_row: usize,
use_color_buffer: bool,
) -> usize {
let size = crossterm::terminal::size().unwrap_or_else(|_| (80, 24));
// render
let mut pos = 0;
let width = size.0 as usize;
let height = size.1 as usize - 1;
let mut frame_buffer = vec![];
for command in draw_commands {
match command {
DrawCommand::DrawString(style, string) => {
for chr in string.chars() {
if chr == '\t' {
for _ in 0..8 {
frame_buffer.push((
' ',
style.foreground.r,
style.foreground.g,
style.foreground.b,
));
}
pos += 8;
} else {
frame_buffer.push((
chr,
style.foreground.r,
style.foreground.g,
style.foreground.b,
));
pos += 1;
}
}
}
DrawCommand::NextLine => {
for _ in 0..(width - pos % width) {
frame_buffer.push((' ', 0, 0, 0));
}
pos += width - pos % width;
}
}
}
let num_frame_buffer_rows = frame_buffer.len() / width;
let buffer_needs_scrolling = num_frame_buffer_rows > height;
// display
let mut ansi_strings = vec![];
let mut normal_chars = vec![];
for c in
&frame_buffer[starting_row * width..std::cmp::min(pos, (starting_row + height) * width)]
{
if use_color_buffer {
ansi_strings.push(ansi_term::Colour::RGB(c.1, c.2, c.3).paint(format!("{}", c.0)));
} else {
normal_chars.push(c.0);
}
}
if buffer_needs_scrolling {
let _ = std::io::stdout().execute(crossterm::cursor::MoveTo(0, 0));
}
if use_color_buffer {
print!("{}", ansi_term::ANSIStrings(&ansi_strings));
} else {
let s: String = normal_chars.into_iter().collect();
print!("{}", s);
}
if buffer_needs_scrolling {
let _ = std::io::stdout().execute(crossterm::cursor::MoveTo(0, size.1));
print!(
"{}",
ansi_term::Colour::Blue.paint("[ESC to quit, arrow keys to move]")
);
}
let _ = std::io::stdout().flush();
num_frame_buffer_rows
}
fn scroll_view_lines_if_needed(draw_commands: Vec<DrawCommand>, use_color_buffer: bool) {
let mut starting_row = 0;
if let Ok(_raw) = crossterm::terminal::enable_raw_mode() {
let mut size = crossterm::terminal::size().unwrap_or_else(|_| (80, 24));
let height = size.1 as usize - 1;
let mut max_bottom_line = paint_textview(&draw_commands, starting_row, use_color_buffer);
// Only scroll if needed
if max_bottom_line > height as usize {
let _ = std::io::stdout().execute(crossterm::cursor::Hide);
loop {
if let Ok(ev) = crossterm::event::read() {
if let crossterm::event::Event::Key(KeyEvent { code, modifiers }) = ev {
match code {
KeyCode::Esc => {
break;
}
KeyCode::Up | KeyCode::Char('k') => {
if starting_row > 0 {
starting_row -= 1;
max_bottom_line = paint_textview(
&draw_commands,
starting_row,
use_color_buffer,
);
}
}
KeyCode::Down | KeyCode::Char('j') => {
if starting_row < (max_bottom_line - height) {
starting_row += 1;
}
max_bottom_line =
paint_textview(&draw_commands, starting_row, use_color_buffer);
}
KeyCode::Char('b')
if modifiers.contains(crossterm::event::KeyModifiers::CONTROL) =>
{
starting_row -= std::cmp::min(height, starting_row);
max_bottom_line =
paint_textview(&draw_commands, starting_row, use_color_buffer);
}
KeyCode::PageUp => {
starting_row -= std::cmp::min(height, starting_row);
max_bottom_line =
paint_textview(&draw_commands, starting_row, use_color_buffer);
}
KeyCode::Char('f')
if modifiers.contains(crossterm::event::KeyModifiers::CONTROL) =>
{
if starting_row < (max_bottom_line - height) {
starting_row += height;
if starting_row > (max_bottom_line - height) {
starting_row = max_bottom_line - height;
}
}
max_bottom_line =
paint_textview(&draw_commands, starting_row, use_color_buffer);
}
KeyCode::PageDown | KeyCode::Char(' ') => {
if starting_row < (max_bottom_line - height) {
starting_row += height;
if starting_row > (max_bottom_line - height) {
starting_row = max_bottom_line - height;
}
}
max_bottom_line =
paint_textview(&draw_commands, starting_row, use_color_buffer);
}
_ => {}
}
}
}
if let Ok(new_size) = crossterm::terminal::size() {
if size != new_size {
size = new_size;
let _ = std::io::stdout().execute(crossterm::terminal::Clear(
crossterm::terminal::ClearType::All,
));
max_bottom_line =
paint_textview(&draw_commands, starting_row, use_color_buffer);
}
}
}
}
let _ = std::io::stdout().execute(crossterm::cursor::Show);
let _ = crossterm::terminal::disable_raw_mode();
}
println!()
}
fn scroll_view(s: &str) {
let mut v = vec![];
for line in s.lines() {
v.push(DrawCommand::DrawString(Style::default(), line.to_string()));
v.push(DrawCommand::NextLine);
}
scroll_view_lines_if_needed(v, false);
}
#[allow(clippy::cognitive_complexity)]
pub fn view_text_value(value: &Value) {
let mut term_width: u64 = textwrap::termwidth() as u64;
let mut tab_width: u64 = 4;
let mut colored_output = true;
let mut true_color = true;
let mut header = true;
let mut line_numbers = true;
let mut grid = true;
let mut vcs_modification_markers = true;
let mut snip = true;
let mut wrapping_mode = bat::WrappingMode::NoWrapping;
let mut use_italics = true;
let mut paging_mode = bat::PagingMode::QuitIfOneScreen;
let mut pager = "less".to_string();
let mut line_ranges = bat::line_range::LineRanges::all();
let mut _highlight_range = "0,0";
let highlight_range_from: u64 = 0;
let highlight_range_to: u64 = 0;
let mut theme = "OneHalfDark".to_string();
if let Ok(config) = nu_cli::data::config::config(Tag::unknown()) {
if let Some(batvars) = config.get("textview") {
for (idx, value) in batvars.row_entries() {
match idx.as_ref() {
"term_width" => {
term_width = match value.as_u64() {
Ok(n) => n,
_ => textwrap::termwidth() as u64,
}
}
"tab_width" => {
tab_width = match value.as_u64() {
Ok(n) => n,
_ => 4u64,
}
}
"colored_output" => {
colored_output = match value.as_bool() {
Ok(b) => b,
_ => true,
}
}
"true_color" => {
true_color = match value.as_bool() {
Ok(b) => b,
_ => true,
}
}
"header" => {
header = match value.as_bool() {
Ok(b) => b,
_ => true,
}
}
"line_numbers" => {
line_numbers = match value.as_bool() {
Ok(b) => b,
_ => true,
}
}
"grid" => {
grid = match value.as_bool() {
Ok(b) => b,
_ => true,
}
}
"vcs_modification_markers" => {
vcs_modification_markers = match value.as_bool() {
Ok(b) => b,
_ => true,
}
}
"snip" => {
snip = match value.as_bool() {
Ok(b) => b,
_ => true,
}
}
"wrapping_mode" => {
wrapping_mode = match value.as_string() {
Ok(s) if s.to_lowercase() == "nowrapping" => {
bat::WrappingMode::NoWrapping
}
Ok(s) if s.to_lowercase() == "character" => {
bat::WrappingMode::Character
}
_ => bat::WrappingMode::NoWrapping,
}
}
"use_italics" => {
use_italics = match value.as_bool() {
Ok(b) => b,
_ => true,
}
}
"paging_mode" => {
paging_mode = match value.as_string() {
Ok(s) if s.to_lowercase() == "always" => bat::PagingMode::Always,
Ok(s) if s.to_lowercase() == "never" => bat::PagingMode::Never,
Ok(s) if s.to_lowercase() == "quitifonescreen" => {
bat::PagingMode::QuitIfOneScreen
}
_ => bat::PagingMode::QuitIfOneScreen,
}
}
"pager" => {
pager = match value.as_string() {
Ok(s) => s,
_ => "less".to_string(),
}
}
"line_ranges" => line_ranges = bat::line_range::LineRanges::all(), // not real sure what to do with this
"highlight_range" => _highlight_range = "0,0", //ignore config value for now
"theme" => {
theme = match value.as_string() {
Ok(s) => s,
_ => "OneDarkHalf".to_string(),
}
}
_ => (),
}
}
}
}
let value_anchor = value.anchor();
if let UntaggedValue::Primitive(Primitive::String(ref s)) = &value.value {
if let Some(source) = value_anchor {
let extension: Option<String> = match source {
let file_path: Option<String> = match source {
AnchorLocation::File(file) => {
let path = Path::new(&file);
path.extension().map(|x| x.to_string_lossy().to_string())
Some(path.to_string_lossy().to_string())
}
AnchorLocation::Url(url) => {
let url = url::Url::parse(&url);
@ -237,7 +151,7 @@ pub fn view_text_value(value: &Value) {
if let Some(mut segments) = url.path_segments() {
if let Some(file) = segments.next_back() {
let path = Path::new(file);
path.extension().map(|x| x.to_string_lossy().to_string())
Some(path.to_string_lossy().to_string())
} else {
None
}
@ -252,38 +166,74 @@ pub fn view_text_value(value: &Value) {
AnchorLocation::Source(_source) => None,
};
match extension {
Some(extension) => {
// Load these once at the start of your program
let ps: SyntaxSet =
syntect::dumps::from_binary(include_bytes!("assets/syntaxes.bin"));
if let Some(syntax) = ps.find_syntax_by_extension(&extension) {
let ts: ThemeSet =
syntect::dumps::from_binary(include_bytes!("assets/themes.bin"));
let mut h = HighlightLines::new(syntax, &ts.themes["OneHalfDark"]);
let mut v = vec![];
for line in s.lines() {
let ranges: Vec<(Style, &str)> = h.highlight(line, &ps);
for range in ranges {
v.push(DrawCommand::DrawString(range.0, range.1.to_string()));
}
v.push(DrawCommand::NextLine);
}
scroll_view_lines_if_needed(v, true);
} else {
scroll_view(s);
}
match file_path {
Some(file_path) => {
// Let bat do it's thing
bat::PrettyPrinter::new()
.input_from_bytes_with_name(s.as_bytes(), file_path)
.term_width(term_width as usize)
.tab_width(Some(tab_width as usize))
.colored_output(colored_output)
.true_color(true_color)
.header(header)
.line_numbers(line_numbers)
.grid(grid)
.vcs_modification_markers(vcs_modification_markers)
.snip(snip)
.wrapping_mode(wrapping_mode)
.use_italics(use_italics)
.paging_mode(paging_mode)
.pager(&pager)
.line_ranges(line_ranges)
.highlight_range(highlight_range_from as usize, highlight_range_to as usize)
.theme(&theme)
.print()
.expect("Error with bat PrettyPrint");
}
_ => {
scroll_view(s);
bat::PrettyPrinter::new()
.input_from_bytes(s.as_bytes())
.term_width(term_width as usize)
.tab_width(Some(tab_width as usize))
.colored_output(colored_output)
.true_color(true_color)
.header(header)
.line_numbers(line_numbers)
.grid(grid)
.vcs_modification_markers(vcs_modification_markers)
.snip(snip)
.wrapping_mode(wrapping_mode)
.use_italics(use_italics)
.paging_mode(paging_mode)
.pager(&pager)
.line_ranges(line_ranges)
.highlight_range(highlight_range_from as usize, highlight_range_to as usize)
.theme(&theme)
.print()
.expect("Error with bat PrettyPrint");
}
}
} else {
scroll_view(s);
bat::PrettyPrinter::new()
.input_from_bytes(s.as_bytes())
.term_width(term_width as usize)
.tab_width(Some(tab_width as usize))
.colored_output(colored_output)
.true_color(true_color)
.header(header)
.line_numbers(line_numbers)
.grid(grid)
.vcs_modification_markers(vcs_modification_markers)
.snip(snip)
.wrapping_mode(wrapping_mode)
.use_italics(use_italics)
.paging_mode(paging_mode)
.pager(&pager)
.line_ranges(line_ranges)
.highlight_range(highlight_range_from as usize, highlight_range_to as usize)
.theme(&theme)
.print()
.expect("Error with bat PrettyPrint");
}
}
}

View File

@ -8,6 +8,7 @@ Use `cal` to display a calendar.
* `-q`, `--quarter`: Display the quarter column
* `-m`, `--month`: Display the month column
* `--full-year` \<integer>: Display a year-long calendar for the specified year
* `--week-start` \<string>: Display the calendar with the specified day as the first day of the week
* `--month-names`: Display the month names instead of integers
## Examples
@ -188,3 +189,16 @@ Use `cal` to display a calendar.
1 │ 2020 │ november │ 8 │ 9 │ 10 │ 11 │ 12 │ 13 │ 14
───┴──────┴──────────┴────────┴────────┴─────────┴───────────┴──────────┴────────┴──────────
```
```shell
> cal -ymq --month-names --week-start-day monday
───┬──────┬─────────┬───────┬────────┬─────────┬───────────┬──────────┬────────┬──────────┬────────
# │ year │ quarter │ month │ monday │ tuesday │ wednesday │ thursday │ friday │ saturday │ sunday
───┼──────┼─────────┼───────┼────────┼─────────┼───────────┼──────────┼────────┼──────────┼────────
0 │ 2020 │ 2 │ june │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7
1 │ 2020 │ 2 │ june │ 8 │ 9 │ 10 │ 11 │ 12 │ 13 │ 14
2 │ 2020 │ 2 │ june │ 15 │ 16 │ 17 │ 18 │ 19 │ 20 │ 21
3 │ 2020 │ 2 │ june │ 22 │ 23 │ 24 │ 25 │ 26 │ 27 │ 28
4 │ 2020 │ 2 │ june │ 29 │ 30 │ │ │ │ │
───┴──────┴─────────┴───────┴────────┴─────────┴───────────┴──────────┴────────┴──────────┴────────
```

46
docs/commands/every.md Normal file
View File

@ -0,0 +1,46 @@
# every
Selects every n-th row of a table, starting from the first one. With the `--skip` flag, every n-th row will be skipped, inverting the original functionality.
Syntax: `> [input-command] | every <stride> {flags}`
## Flags
* `--skip`, `-s`: Skip the rows that would be returned, instead of selecting them
## Examples
```shell
> open contacts.csv
───┬─────────┬──────┬─────────────────
# │ first │ last │ email
───┼─────────┼──────┼─────────────────
0 │ John │ Doe │ doe.1@email.com
1 │ Jane │ Doe │ doe.2@email.com
2 │ Chris │ Doe │ doe.3@email.com
3 │ Francis │ Doe │ doe.4@email.com
4 │ Stella │ Doe │ doe.5@email.com
───┴─────────┴──────┴─────────────────
```
```shell
> open contacts.csv | every 2
───┬─────────┬──────┬─────────────────
# │ first │ last │ email
───┼─────────┼──────┼─────────────────
0 │ John │ Doe │ doe.1@email.com
2 │ Chris │ Doe │ doe.3@email.com
4 │ Stella │ Doe │ doe.5@email.com
───┴─────────┴──────┴─────────────────
```
```shell
> open contacts.csv | every 2 --skip
───┬─────────┬──────┬─────────────────
# │ first │ last │ email
───┼─────────┼──────┼─────────────────
1 │ Jane │ Doe │ doe.2@email.com
3 │ Francis │ Doe │ doe.4@email.com
───┴─────────┴──────┴─────────────────
```

View File

@ -1,97 +1,154 @@
# math
Mathematical functions that generally only operate on a list of numbers (integers, decimals, bytes) and tables.
Currently the following functions are implemented:
`math average` Get the average of a list of number
`math min` Get the minimum of a list of numbers
`math max` Get the maximum of a list of numbers
* `math avg`: Finds the average of a list of numbers or tables
* `math min`: Finds the minimum within a list of numbers or tables
* `math max`: Finds the maximum within a list of numbers or tables
* `math median`: Finds the median of a list of numbers or tables
* `math sum`: Finds the sum of a list of numbers or tables
However, the mathematical functions like `min` and `max` are more permissive and also work on `Dates`.
## Examples
To get the average of the file sizes in a directory, simply pipe the size column from the ls command to the average command.
### List of Numbers (Integers, Decimals, Bytes)
```shell
> ls
# │ name │ type │ size │ modified
# │ name │ type │ size │ modified
────┼────────────────────┼──────┼──────────┼─────────────
0 │ CODE_OF_CONDUCT.md │ File │ 3.4 KB │ 4 days ago
1 │ CONTRIBUTING.md │ File │ 1.3 KB │ 4 days ago
2 │ Cargo.lock │ File │ 106.3 KB │ 6 mins ago
3 │ Cargo.toml │ File │ 4.6 KB │ 3 days ago
4 │ LICENSE │ File │ 1.1 KB │ 4 days ago
5 │ Makefile.toml │ File │ 449 B │ 4 days ago
6 │ README.md │ File │ 16.0 KB │ 6 mins ago
7 │ TODO.md │ File │ 0 B │ 6 mins ago
8 │ assets │ Dir │ 128 B │ 4 days ago
9 │ build.rs │ File │ 78 B │ 4 days ago
10 │ crates │ Dir │ 672 B │ 3 days ago
11 │ debian │ Dir │ 352 B │ 4 days ago
12 │ docker │ Dir │ 288 B │ 4 days ago
13 │ docs │ Dir │ 160 B │ 4 days ago
14 │ features.toml │ File │ 632 B │ 4 days ago
15 │ images │ Dir │ 160 B │ 4 days ago
16 │ justfile │ File │ 234 B │ 3 days ago
17 │ rustfmt.toml │ File │ 16 B │ 4 days ago
18 │ src │ Dir │ 128 B │ 4 days ago
19 │ target │ Dir │ 192 B │ 8 hours ago
20 │ tests │ Dir │ 192 B │ 4 days ago
0 │ CODE_OF_CONDUCT.md │ File │ 3.4 KB │ 4 days ago
1 │ CONTRIBUTING.md │ File │ 1.3 KB │ 4 days ago
2 │ Cargo.lock │ File │ 106.3 KB │ 6 mins ago
3 │ Cargo.toml │ File │ 4.6 KB │ 3 days ago
4 │ LICENSE │ File │ 1.1 KB │ 4 days ago
5 │ Makefile.toml │ File │ 449 B │ 4 days ago
6 │ README.md │ File │ 16.0 KB │ 6 mins ago
7 │ TODO.md │ File │ 0 B │ 6 mins ago
8 │ assets │ Dir │ 128 B │ 4 days ago
9 │ build.rs │ File │ 78 B │ 4 days ago
10 │ crates │ Dir │ 672 B │ 3 days ago
11 │ debian │ Dir │ 352 B │ 4 days ago
12 │ docker │ Dir │ 288 B │ 4 days ago
13 │ docs │ Dir │ 160 B │ 4 days ago
14 │ features.toml │ File │ 632 B │ 4 days ago
15 │ images │ Dir │ 160 B │ 4 days ago
16 │ justfile │ File │ 234 B │ 3 days ago
17 │ rustfmt.toml │ File │ 16 B │ 4 days ago
18 │ src │ Dir │ 128 B │ 4 days ago
19 │ target │ Dir │ 192 B │ 8 hours ago
20 │ tests │ Dir │ 192 B │ 4 days ago
```
> ls | get size | math average
```shell
> ls | get size | math avg
───┬────────
0 │ 6.5 KB
# │
───┼────────
0 │ 7.2 KB
───┴────────
```
```shell
> ls | get size | math min
───┬─────
0 │ 0 B
# │
───┼─────
0 │ 0 B
───┴─────
> ls | get size | math max
───┬──────────
0 │ 106.3 KB
───┴──────────
```
# Dates
```shell
───┬──────────
# │
───┼──────────
0 │ 113.6 KB
───┴──────────
```
```shell
> ls | get size | math median
───┬───────
# │
───┼───────
0 │ 320 B
───┴───────
```
```shell
> ls | get size | math sum
───┬──────────
# │
───┼──────────
0 │ 143.6 KB
───┴──────────
```
### Dates
```shell
> ls | get modified | math min
2020-06-09 17:25:51.798743222 UTC
```
```shell
> ls | get modified | math max
2020-06-14 05:49:59.637449186 UT
```
### Operations on tables
```shell
> pwd | split row / | size
> pwd | split row / | size
───┬───────┬───────┬───────┬────────────
# │ lines │ words │ chars │ max length
# │ lines │ words │ chars │ max length
───┼───────┼───────┼───────┼────────────
0 │ 0 │ 1 │ 5 │ 5
1 │ 0 │ 1 │ 7 │ 7
2 │ 0 │ 1 │ 9 │ 9
3 │ 0 │ 1 │ 7 │ 7
0 │ 0 │ 1 │ 5 │ 5
1 │ 0 │ 1 │ 11 │ 11
2 │ 0 │ 1 │ 11 │ 11
3 │ 0 │ 1 │ 4 │ 4
4 │ 0 │ 2 │ 12 │ 12
5 │ 0 │ 1 │ 7 │ 7
───┴───────┴───────┴───────┴────────────
```
```shell
> pwd | split row / | size | math max
───────────┬───
lines │ 0
words │ 1
chars │ 9
max length │ 9
────────────┴───
────────────┬────
lines │ 0
words │ 2
chars │ 12
max length │ 12
────────────┴────
```
> pwd | split row / | size | math average
```shell
> pwd | split row / | size | math avg
────────────┬────────
lines │ 0.0000
words │ 1.0000
chars │ 7.0000
max length │ 7.0000
lines │ 0.0000
words │ 1.1666
chars │ 8.3333
max length │ 8.3333
────────────┴────────
```
## Errors
`math` functions are aggregation functions so empty lists are invalid
To get the sum of the characters that make up your present working directory.
```shell
> echo [] | math average
> pwd | split row / | size | get chars | math sum
50
```
## Errors
`math` functions are aggregation functions so empty lists are invalid
```shell
> echo [] | math avg
error: Error: Unexpected: Cannot perform aggregate math operation on empty data
```
@ -99,10 +156,6 @@ Note `math` functions only work on list of numbers (integers, decimals, bytes) a
then unexpected results can occur.
```shell
> echo [1 2 a ] | math average
> echo [1 2 a ] | math avg
0
```

View File

@ -41,7 +41,7 @@ Applies the subcommand to a value or a table.
1 │ │ filesystem │
━━━┷━━━┷━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
> echo "1, 2, 3" | split row "," | str to-int | sum
> echo "1, 2, 3" | split row "," | str to-int | math sum
━━━━━━━━━
<value>
─────────

View File

@ -1,44 +0,0 @@
# sum
This command allows you to calculate the sum of values in a column.
## Examples
To get the sum of the file sizes in a directory, simply pipe the size column from the ls command to the sum command.
```shell
> ls | get size | sum
━━━━━━━━━
value
━━━━━━━━━
51.0 MB
━━━━━━━━━
```
To get the sum of the characters that make up your present working directory.
```shell
> pwd | split-row / | size | get chars | sum
━━━━━━━━━
<value>
━━━━━━━━━
21
━━━━━━━━━
```
Note that sum only works for integer and byte values. If the shell doesn't recognize the values in a column as one of those types, it will return an error.
One way to solve this is to convert each row to an integer when possible and then pipe the result to `sum`
```shell
> open tests/fixtures/formats/caco3_plastics.csv | get tariff_item | sum
error: Unrecognized type in stream: Primitive(String("2509000000"))
- shell:1:0
1 | open tests/fixtures/formats/caco3_plastics.csv | get tariff_item | sum
| ^^^^ source
```
```shell
> open tests/fixtures/formats/caco3_plastics.csv | get tariff_item | str --to-int | sum
━━━━━━━━━━━━━
<value>
─────────────
29154639996
━━━━━━━━━━━━━
```

View File

@ -0,0 +1,55 @@
# textview config
The configuration for textview, which is used to autoview text files, uses [bat](https://docs.rs/bat/0.15.4/bat/struct.PrettyPrinter.html). The textview configurtion will **not** use any existing `bat` configuration you may have.
### Configuration Points and Defaults
| config point | definition | implemented |
| - | - | - |
| term_width | The character width of the terminal (default: autodetect) | yes |
| tab_width | The width of tab characters (default: None - do not turn tabs to spaces) | yes |
| colored_output | Whether or not the output should be colorized (default: true) | yes |
| true_color | Whether or not to output 24bit colors (default: true) | yes |
| header | Whether to show a header with the file name | yes |
| line_numbers | Whether to show line numbers | yes |
| grid | Whether to paint a grid, separating line numbers, git changes and the code | yes |
| vcs_modification_markers | Whether to show modification markers for VCS changes. This has no effect if the git feature is not activated. | yes |
| snip | Whether to show "snip" markers between visible line ranges (default: no) | yes |
| wrapping_mode | Text wrapping mode (default: do not wrap), options (Character, NoWrapping) | yes |
| use_italics | Whether or not to use ANSI italics (default: off) | yes |
| paging_mode | If and how to use a pager (default: no paging), options (Always, QuitIfOneScreen, Never) | yes |
| pager | Specify the command to start the pager (default: use "less") | yes |
| line_ranges | Specify the lines that should be printed (default: all) | no |
| highlight | Specify a line that should be highlighted (default: none). This can be called multiple times to highlight more than one line. See also: highlight_range. | no |
| highlight_range | Specify a range of lines that should be highlighted (default: none). This can be called multiple times to highlight more than one range of lines. | no |
| theme | Specify the highlighting theme (default: OneHalfDark) | yes |
### Example textview confguration for `config.toml`
```toml
[textview]
term_width = "default"
tab_width = 4
colored_output = true
true_color = true
header = true
line_numbers = false
grid = false
vcs_modification_markers = true
snip = true
wrapping_mode = "NoWrapping"
use_italics = true
paging_mode = "QuitIfOneScreen"
pager = "less"
theme = "TwoDark"
```
### Example Usage
```
> open src/main.rs
```
```
> cat some_file.txt | textview
```
```
> fetch https://www.jonathanturner.org/feed.xml --raw
```
### Help
For a more detailed description of the configuration points that textview uses, please visit the `bat` repo at https://github.com/sharkdp/bat

View File

@ -34,3 +34,16 @@ Yehuda,Katz,10/11/2013,A
1 │ B
━━━┷━━━━━━━━━
```
### Counting
`--count` or `-c` is the flag to output a `count` column.
```
> `open test.csv | get type | uniq -c`
───┬───────┬───────
# │ value │ count
───┼───────┼───────
0 │ A │ 3
1 │ B │ 2
───┴───────┴───────
```

View File

@ -198,7 +198,7 @@ fn echoing_ranges() {
let actual = nu!(
cwd: ".",
r#"
echo 1..3 | sum
echo 1..3 | math sum
"#
);