--no-edit

This commit is contained in:
Yehuda Katz 2019-11-04 07:47:03 -08:00
parent 388fc24191
commit cdb0eeafa2
84 changed files with 3927 additions and 1402 deletions

112
Cargo.lock generated
View File

@ -314,6 +314,17 @@ dependencies = [
"vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "clicolors-control"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "clipboard"
version = "0.5.0"
@ -365,6 +376,21 @@ dependencies = [
"yaml-rust 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "console"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"clicolors-control 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"encode_unicode 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"termios 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "constant_time_eq"
version = "0.1.4"
@ -814,6 +840,11 @@ dependencies = [
"futures-sink-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "futures-core"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "futures-core-preview"
version = "0.3.0-alpha.19"
@ -834,6 +865,17 @@ name = "futures-io-preview"
version = "0.3.0-alpha.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "futures-macro"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "futures-preview"
version = "0.3.0-alpha.19"
@ -852,11 +894,30 @@ name = "futures-sink-preview"
version = "0.3.0-alpha.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "futures-task"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "futures-timer"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "futures-util"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"futures-core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"futures-macro 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"futures-task 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"pin-utils 0.1.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro-nested 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
"slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "futures-util-preview"
version = "0.3.0-alpha.19"
@ -1616,6 +1677,7 @@ dependencies = [
"chrono-humanize 0.0.11 (registry+https://github.com/rust-lang/crates.io-index)",
"clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
"clipboard 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
"console 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
"crossterm 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
"csv 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"ctrlc 3.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1624,6 +1686,7 @@ dependencies = [
"dunce 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"futures-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)",
"futures-timer 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"futures-util 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"futures_codec 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
"getset 0.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
"git2 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1645,6 +1708,7 @@ dependencies = [
"num-traits 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)",
"onig_sys 69.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"pin-utils 0.1.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)",
"pretty 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
"pretty-hex 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"pretty_assertions 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
"pretty_env_logger 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1672,6 +1736,7 @@ dependencies = [
"syntect 3.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"term 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
"termcolor 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
"textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"toml 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)",
"trash 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1917,6 +1982,14 @@ name = "ppv-lite86"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "pretty"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"typed-arena 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "pretty-hex"
version = "0.1.1"
@ -1956,6 +2029,21 @@ dependencies = [
"unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "proc-macro-hack"
version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "proc-macro-nested"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "proc-macro2"
version = "1.0.6"
@ -2654,6 +2742,14 @@ dependencies = [
"wincolor 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "termios"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "textwrap"
version = "0.11.0"
@ -2731,6 +2827,11 @@ dependencies = [
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "typed-arena"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "typenum"
version = "1.11.2"
@ -3127,11 +3228,13 @@ dependencies = [
"checksum chrono 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e8493056968583b0193c1bb04d6f7684586f3726992d6c573261941a895dbd68"
"checksum chrono-humanize 0.0.11 (registry+https://github.com/rust-lang/crates.io-index)" = "eb2ff48a655fe8d2dae9a39e66af7fd8ff32a879e8c4e27422c25596a8b5e90d"
"checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9"
"checksum clicolors-control 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "90082ee5dcdd64dc4e9e0d37fbf3ee325419e39c0092191e0393df65518f741e"
"checksum clipboard 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "25a904646c0340239dcf7c51677b33928bf24fdf424b79a57909c0109075b2e7"
"checksum clipboard-win 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e3a093d6fed558e5fe24c3dfc85a68bb68f1c824f440d3ba5aca189e2998786b"
"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
"checksum codepage 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8b0e9222c0cdf2c6ac27d73f664f9520266fa911c3106329d359f8861cb8bde9"
"checksum config 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "f9107d78ed62b3fa5a86e7d18e647abed48cfd8f8fab6c72f4cdb982d196f7e6"
"checksum console 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f5d540c2d34ac9dd0deb5f3b5f54c36c79efa78f6b3ad19106a554d07a7b5d9f"
"checksum constant_time_eq 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "995a44c877f9212528ccc74b21a232f66ad69001e40ede5bcee2ac9ef2657120"
"checksum core-foundation 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "25b9e03f145fd4f2bf705e07b900cd41fc636598fe5dc452fd0db1441c3f496d"
"checksum core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e7ca8a5221364ef15ce201e8ed2f609fc312682a8f4e0e3d4aa5879764e0fa3b"
@ -3184,12 +3287,16 @@ dependencies = [
"checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
"checksum futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)" = "1b980f2816d6ee8673b6517b52cb0e808a180efc92e5c19d02cdda79066703ef"
"checksum futures-channel-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)" = "d5e5f4df964fa9c1c2f8bddeb5c3611631cacd93baf810fc8bb2fb4b495c263a"
"checksum futures-core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "79564c427afefab1dfb3298535b21eda083ef7935b4f0ecbfcb121f0aec10866"
"checksum futures-core-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)" = "b35b6263fb1ef523c3056565fa67b1d16f0a8604ff12b11b08c25f28a734c60a"
"checksum futures-executor-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)" = "75236e88bd9fe88e5e8bfcd175b665d0528fe03ca4c5207fabc028c8f9d93e98"
"checksum futures-io-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)" = "f4914ae450db1921a56c91bde97a27846287d062087d4a652efc09bb3a01ebda"
"checksum futures-macro 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "52e7c56c15537adb4f76d0b7a76ad131cb4d2f4f32d3b0bcabcbe1c7c5e87764"
"checksum futures-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)" = "3b1dce2a0267ada5c6ff75a8ba864b4e679a9e2aa44262af7a3b5516d530d76e"
"checksum futures-sink-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)" = "86f148ef6b69f75bb610d4f9a2336d4fc88c4b5b67129d1a340dd0fd362efeec"
"checksum futures-task 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0bae52d6b29cf440e298856fec3965ee6fa71b06aa7495178615953fd669e5f9"
"checksum futures-timer 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a1de7508b218029b0f01662ed8f61b1c964b3ae99d6f25462d0f55a595109df6"
"checksum futures-util 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c0d66274fb76985d3c62c886d1da7ac4c0903a8c9f754e8fe0f35a6a6cc39e76"
"checksum futures-util-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)" = "5ce968633c17e5f97936bd2797b6e38fb56cf16a7422319f7ec2e30d3c470e8d"
"checksum futures_codec 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "36552cd31353fd135114510d53b8d120758120c36aa636a9341970f9efb1e4a0"
"checksum gethostname 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d4ab273ca2a31eb6ca40b15837ccf1aa59a43c5db69ac10c542be342fae2e01d"
@ -3297,10 +3404,13 @@ dependencies = [
"checksum png 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8422b27bb2c013dd97b9aef69e161ce262236f49aaf46a0489011c8ff0264602"
"checksum podio 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "780fb4b6698bbf9cf2444ea5d22411cef2953f0824b98f33cf454ec5615645bd"
"checksum ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b"
"checksum pretty 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f60c0d9f6fc88ecdd245d90c1920ff76a430ab34303fc778d33b1d0a4c3bf6d3"
"checksum pretty-hex 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "be91bcc43e73799dc46a6c194a55e7aae1d86cc867c860fd4a436019af21bd8c"
"checksum pretty_assertions 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3f81e1644e1b54f5a68959a29aa86cde704219254669da328ecfdf6a1f09d427"
"checksum pretty_env_logger 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "717ee476b1690853d222af4634056d830b5197ffd747726a9a1eee6da9f49074"
"checksum prettytable-rs 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0fd04b170004fa2daccf418a7f8253aaf033c27760b5f225889024cf66d7ac2e"
"checksum proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)" = "ecd45702f76d6d3c75a80564378ae228a85f0b59d2f3ed43c91b4a69eb2ebfc5"
"checksum proc-macro-nested 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "369a6ed065f249a159e06c45752c780bda2fb53c995718f9e484d08daa9eb42e"
"checksum proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "9c9e470a8dc4aeae2dee2f335e8f533e2d4b347e1434e5671afc49b054592f27"
"checksum ptree 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6b0a3be00b19ee7bd33238c1c523a7ab4df697345f6b36f90827a7860ea938d4"
"checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0"
@ -3377,6 +3487,7 @@ dependencies = [
"checksum term 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "edd106a334b7657c10b7c540a0106114feadeb4dc314513e97df481d5d966f42"
"checksum term_size 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9e5b9a66db815dcfd2da92db471106457082577c3c278d4138ab3e3b4e189327"
"checksum termcolor 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "96d6098003bde162e4277c70665bd87c326f5a0c3f3fbfb285787fa482d54e6e"
"checksum termios 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "72b620c5ea021d75a735c943269bb07d30c9b77d6ac6b236bc8b5c496ef05625"
"checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
"checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b"
"checksum threadpool 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e2f0c90a5f3459330ac8bc0d2f879c693bb7a2f59689c1083fc4ef83834da865"
@ -3386,6 +3497,7 @@ dependencies = [
"checksum toml 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "758664fc71a3a69038656bee8b6be6477d2a6c315a6b81f7081f591bffa4111f"
"checksum toml 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "01d1404644c8b12b16bfcffa4322403a91a451584daaaa7c28d3152e6cbc98cf"
"checksum trash 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f2f24d31505f49e989b1ee2c03c323251f6763d5907d471b71192dac92e323f8"
"checksum typed-arena 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a9b2228007eba4120145f785df0f6c92ea538f5a3635a612ecf4e334c8c1446d"
"checksum typenum 1.11.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6d2783fe2d6b8c1101136184eb41be8b1ad379e4657050b8aaff0c79ee7575f9"
"checksum umask 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "d3ec2e5aeb4aadd510db9124513a7fec4a9c3a331b7f57aa519440dab3707067"
"checksum unicase 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6"

View File

@ -79,6 +79,10 @@ cfg-if = "0.1"
strip-ansi-escapes = "0.1.0"
calamine = "0.16"
umask = "0.1"
futures-util = "0.3.0"
pretty = { version = "0.5.2" }
termcolor = "1.0.5"
console = "0.9.1"
neso = { version = "0.5.0", optional = true }
crossterm = { version = "0.10.2", optional = true }
@ -92,6 +96,7 @@ ptree = {version = "0.2" }
image = { version = "0.22.2", default_features = false, features = ["png_codec", "jpeg"], optional = true }
starship = { version = "0.26.4", optional = true}
[features]
default = ["textview", "sys", "ps"]
raw-key = ["rawkey", "neso"]

View File

@ -4,21 +4,12 @@ command = "lalrpop"
args = ["src/parser/parser.lalrpop"]
[tasks.baseline]
dependencies = ["lalrpop"]
[tasks.build]
command = "cargo"
args = ["build"]
dependencies = ["lalrpop"]
args = ["build", "--bins"]
[tasks.run]
command = "cargo"
args = ["run", "--release"]
dependencies = ["baseline"]
[tasks.release]
command = "cargo"
args = ["build", "--release"]
args = ["run"]
dependencies = ["baseline"]
[tasks.test]

View File

@ -9,6 +9,7 @@ use crate::context::Context;
use crate::data::config;
use crate::data::Value;
pub(crate) use crate::errors::ShellError;
use crate::fuzzysearch::{interactive_fuzzy_search, SelectionResult};
#[cfg(not(feature = "starship-prompt"))]
use crate::git::current_branch;
use crate::parser::registry::Signature;
@ -260,7 +261,7 @@ pub async fn cli() -> Result<(), Box<dyn Error>> {
whole_stream_command(Nth),
whole_stream_command(Next),
whole_stream_command(Previous),
whole_stream_command(Debug),
// whole_stream_command(Debug),
whole_stream_command(Shells),
whole_stream_command(SplitColumn),
whole_stream_command(SplitRow),
@ -325,7 +326,9 @@ pub async fn cli() -> Result<(), Box<dyn Error>> {
whole_stream_command(SplitBy),
whole_stream_command(Table),
whole_stream_command(Version),
whole_stream_command(What),
whole_stream_command(Which),
whole_stream_command(DebugValue),
]);
cfg_if::cfg_if! {
@ -419,13 +422,47 @@ pub async fn cli() -> Result<(), Box<dyn Error>> {
let mut readline = Err(ReadlineError::Eof);
while let Some(ref cmd) = initial_command {
readline = rl.readline_with_initial(&prompt, (&cmd, ""));
if let Err(ReadlineError::Eof) = &readline {
// Fuzzy search in history
let lines = rl.history().iter().rev().map(|s| s.as_str()).collect();
let selection = interactive_fuzzy_search(&lines, 5); // Clears last line with prompt
match selection {
SelectionResult::Selected(line) => {
outln!("{}{}", &prompt, &line); // TODO: colorize prompt
readline = Ok(line.clone());
initial_command = None;
}
SelectionResult::Edit(line) => {
initial_command = Some(line);
}
SelectionResult::NoSelection => {
readline = Ok("".to_string());
initial_command = None;
}
}
} else {
initial_command = None;
}
}
match process_line(readline, &mut context).await {
let line = process_line(readline, &mut context).await;
match line {
LineResult::Success(line) => {
rl.add_history_entry(line.clone());
let _ = rl.save_history(&History::path());
context.maybe_print_errors(Text::from(line));
}
LineResult::Error(line, err) => {
rl.add_history_entry(line.clone());
let _ = rl.save_history(&History::path());
context.with_host(|host| {
print_err(err, host, &Text::from(line.clone()));
});
context.maybe_print_errors(Text::from(line.clone()));
}
LineResult::CtrlC => {
@ -451,15 +488,6 @@ pub async fn cli() -> Result<(), Box<dyn Error>> {
}
}
LineResult::Error(line, err) => {
rl.add_history_entry(line.clone());
let _ = rl.save_history(&History::path());
context.with_host(|host| {
print_err(err, host, &Text::from(line));
})
}
LineResult::Break => {
break;
}
@ -703,7 +731,7 @@ async fn process_line(readline: Result<String, ReadlineError>, ctx: &mut Context
Err(ReadlineError::Interrupted) => LineResult::CtrlC,
Err(ReadlineError::Eof) => LineResult::Break,
Err(err) => {
println!("Error: {:?}", err);
outln!("Error: {:?}", err);
LineResult::Break
}
}
@ -725,9 +753,9 @@ fn classify_pipeline(
.map_err(|err| err.into());
if log_enabled!(target: "nu::expand_syntax", log::Level::Debug) {
println!("");
outln!("");
ptree::print_tree(&iterator.expand_tracer().print(source.clone())).unwrap();
println!("");
outln!("");
}
result

View File

@ -16,6 +16,7 @@ pub(crate) mod count;
pub(crate) mod cp;
pub(crate) mod date;
pub(crate) mod debug;
pub(crate) mod debug_value;
pub(crate) mod echo;
pub(crate) mod enter;
pub(crate) mod env;
@ -85,6 +86,7 @@ pub(crate) mod to_url;
pub(crate) mod to_yaml;
pub(crate) mod trim;
pub(crate) mod version;
pub(crate) mod what;
pub(crate) mod where_;
pub(crate) mod which_;
@ -101,7 +103,7 @@ pub(crate) use config::Config;
pub(crate) use count::Count;
pub(crate) use cp::Cpy;
pub(crate) use date::Date;
pub(crate) use debug::Debug;
pub(crate) use debug_value::DebugValue;
pub(crate) use echo::Echo;
pub(crate) use enter::Enter;
pub(crate) use env::Env;
@ -173,5 +175,6 @@ pub(crate) use to_url::ToURL;
pub(crate) use to_yaml::ToYAML;
pub(crate) use trim::Trim;
pub(crate) use version::Version;
pub(crate) use what::What;
pub(crate) use where_::Where;
pub(crate) use which_::Which;

View File

@ -46,7 +46,9 @@ pub fn autoview(
Ok(OutputStream::new(async_stream! {
let mut output_stream: OutputStream = context.input.into();
match output_stream.try_next().await {
let next = output_stream.try_next().await;
match next {
Ok(Some(x)) => {
match output_stream.try_next().await {
Ok(Some(y)) => {
@ -91,7 +93,25 @@ pub fn autoview(
let raw = raw.clone();
let mut command_args = raw.with_input(new_input.into());
let input: Vec<Tagged<Value>> = new_input.into();
if input.len() > 0 && input.iter().all(|value| value.is_error()) {
let first = &input[0];
let mut host = context.host.clone();
let mut host = match host.lock() {
Err(err) => {
errln!("Unexpected error acquiring host lock: {:?}", err);
return;
}
Ok(val) => val
};
crate::cli::print_err(first.item.expect_error(), &*host, &context.source);
return;
}
let mut command_args = raw.with_input(input);
let mut named_args = NamedArguments::new();
named_args.insert_optional("start_number", Some(Expression::number(current_idx, Tag::unknown())));
command_args.call_info.args.named = Some(named_args);
@ -99,6 +119,7 @@ pub fn autoview(
let result = table.run(command_args, &context.commands);
result.collect::<Vec<_>>().await;
if finished {
break;
} else {
@ -120,14 +141,14 @@ pub fn autoview(
let result = text.run(raw.with_input(stream.into()), &context.commands);
result.collect::<Vec<_>>().await;
} else {
println!("{}", s);
outln!("{}", s);
}
}
Tagged {
item: Value::Primitive(Primitive::String(s)),
..
} => {
println!("{}", s);
outln!("{}", s);
}
Tagged { item: Value::Primitive(Primitive::Binary(ref b)), .. } => {
@ -138,7 +159,7 @@ pub fn autoview(
result.collect::<Vec<_>>().await;
} else {
use pretty_hex::*;
println!("{:?}", b.hex_dump());
outln!("{:?}", b.hex_dump());
}
}
@ -152,7 +173,7 @@ pub fn autoview(
let result = table.run(raw.with_input(stream.into()), &context.commands);
result.collect::<Vec<_>>().await;
} else {
println!("{:?}", item);
outln!("{:?}", item);
}
}
}
@ -161,7 +182,7 @@ pub fn autoview(
}
}
_ => {
//println!("<no results>");
//outln!("<no results>");
}
}

View File

@ -165,8 +165,7 @@ impl InternalCommand {
trace!(target: "nu::run::internal", "{}", self.args.debug(&source));
}
let objects: InputStream =
trace_stream!(target: "nu::trace_stream::internal", "input" = input.objects);
let objects: InputStream = trace_stream!(target: "nu::trace_stream::internal", source: source, "input" = input.objects);
let command = context.expect_command(&self.name);
@ -180,11 +179,14 @@ impl InternalCommand {
)
};
let result = trace_out_stream!(target: "nu::trace_stream::internal", source: &source, "output" = result);
let result = trace_out_stream!(target: "nu::trace_stream::internal", source: source, "output" = result);
let mut result = result.values;
let mut context = context.clone();
let stream = async_stream! {
let mut soft_errs: Vec<ShellError> = vec![];
let mut yielded = false;
while let Some(item) = result.next().await {
match item {
Ok(ReturnSuccess::Action(action)) => match action {
@ -192,6 +194,10 @@ impl InternalCommand {
context.shell_manager.set_path(path);
}
CommandAction::Exit => std::process::exit(0), // TODO: save history.txt
CommandAction::Error(err) => {
context.error(err);
break;
}
CommandAction::EnterHelpShell(value) => {
match value {
Tagged {
@ -237,11 +243,28 @@ impl InternalCommand {
},
Ok(ReturnSuccess::Value(v)) => {
yielded = true;
yield Ok(v);
}
Err(x) => {
yield Ok(Value::Error(x).tagged_unknown());
Ok(ReturnSuccess::DebugValue(v)) => {
yielded = true;
let doc = v.item.pretty_doc();
let mut buffer = termcolor::Buffer::ansi();
doc.render_raw(
context.with_host(|host| host.width() - 5),
&mut crate::parser::debug::TermColored::new(&mut buffer),
).unwrap();
let value = String::from_utf8_lossy(buffer.as_slice());
yield Ok(Value::string(value).tagged_unknown())
}
Err(err) => {
context.error(err);
break;
}
}

View File

@ -68,7 +68,7 @@ impl CallInfo {
#[derive(Getters)]
#[get = "pub(crate)"]
pub struct CommandArgs {
pub host: Arc<Mutex<dyn Host>>,
pub host: Arc<Mutex<Box<dyn Host>>>,
pub ctrl_c: Arc<AtomicBool>,
pub shell_manager: ShellManager,
pub call_info: UnevaluatedCallInfo,
@ -78,7 +78,7 @@ pub struct CommandArgs {
#[derive(Getters, Clone)]
#[get = "pub(crate)"]
pub struct RawCommandArgs {
pub host: Arc<Mutex<dyn Host>>,
pub host: Arc<Mutex<Box<dyn Host>>>,
pub ctrl_c: Arc<AtomicBool>,
pub shell_manager: ShellManager,
pub call_info: UnevaluatedCallInfo,
@ -94,6 +94,10 @@ impl RawCommandArgs {
input: input.into(),
}
}
pub fn source(&self) -> Text {
self.call_info.source.clone()
}
}
impl std::fmt::Debug for CommandArgs {
@ -128,13 +132,18 @@ impl CommandArgs {
))
}
pub fn process<'de, T: Deserialize<'de>>(
pub fn source(&self) -> Text {
self.call_info.source.clone()
}
pub fn process<'de, T: Deserialize<'de>, O: ToOutputStream>(
self,
registry: &CommandRegistry,
callback: fn(T, RunnableContext) -> Result<OutputStream, ShellError>,
) -> Result<RunnableArgs<T>, ShellError> {
callback: fn(T, RunnableContext) -> Result<O, ShellError>,
) -> Result<RunnableArgs<T, O>, ShellError> {
let shell_manager = self.shell_manager.clone();
let host = self.host.clone();
let source = self.source();
let ctrl_c = self.ctrl_c.clone();
let args = self.evaluate_once(registry)?;
let call_info = args.call_info.clone();
@ -147,6 +156,7 @@ impl CommandArgs {
context: RunnableContext {
input,
commands: registry.clone(),
source,
shell_manager,
name: name_tag,
host,
@ -170,6 +180,7 @@ impl CommandArgs {
let shell_manager = self.shell_manager.clone();
let host = self.host.clone();
let source = self.source();
let ctrl_c = self.ctrl_c.clone();
let args = self.evaluate_once(registry)?;
let call_info = args.call_info.clone();
@ -183,6 +194,7 @@ impl CommandArgs {
context: RunnableContext {
input,
commands: registry.clone(),
source,
shell_manager,
name: name_tag,
host,
@ -208,7 +220,8 @@ impl RunnablePerItemContext {
pub struct RunnableContext {
pub input: InputStream,
pub shell_manager: ShellManager,
pub host: Arc<Mutex<dyn Host>>,
pub host: Arc<Mutex<Box<dyn Host>>>,
pub source: Text,
pub ctrl_c: Arc<AtomicBool>,
pub commands: CommandRegistry,
pub name: Tag,
@ -232,15 +245,15 @@ impl<T> RunnablePerItemArgs<T> {
}
}
pub struct RunnableArgs<T> {
pub struct RunnableArgs<T, O: ToOutputStream> {
args: T,
context: RunnableContext,
callback: fn(T, RunnableContext) -> Result<OutputStream, ShellError>,
callback: fn(T, RunnableContext) -> Result<O, ShellError>,
}
impl<T> RunnableArgs<T> {
impl<T, O: ToOutputStream> RunnableArgs<T, O> {
pub fn run(self) -> Result<OutputStream, ShellError> {
(self.callback)(self.args, self.context)
(self.callback)(self.args, self.context).map(|v| v.to_output_stream())
}
}
@ -387,6 +400,7 @@ impl EvaluatedCommandArgs {
pub enum CommandAction {
ChangePath(String),
Exit,
Error(ShellError),
EnterShell(String),
EnterValueShell(Tagged<Value>),
EnterHelpShell(Tagged<Value>),
@ -396,16 +410,17 @@ pub enum CommandAction {
}
impl FormatDebug for CommandAction {
fn fmt_debug(&self, f: &mut DebugFormatter, _source: &str) -> fmt::Result {
fn fmt_debug(&self, f: &mut DebugFormatter, source: &str) -> fmt::Result {
match self {
CommandAction::ChangePath(s) => write!(f, "action:change-path={}", s),
CommandAction::Exit => write!(f, "action:exit"),
CommandAction::Error(_) => write!(f, "action:error"),
CommandAction::EnterShell(s) => write!(f, "action:enter-shell={}", s),
CommandAction::EnterValueShell(t) => {
write!(f, "action:enter-value-shell={:?}", t.debug())
write!(f, "action:enter-value-shell={}", t.debug(source))
}
CommandAction::EnterHelpShell(t) => {
write!(f, "action:enter-help-shell={:?}", t.debug())
write!(f, "action:enter-help-shell={}", t.debug(source))
}
CommandAction::PreviousShell => write!(f, "action:previous-shell"),
CommandAction::NextShell => write!(f, "action:next-shell"),
@ -417,6 +432,7 @@ impl FormatDebug for CommandAction {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ReturnSuccess {
Value(Tagged<Value>),
DebugValue(Tagged<Value>),
Action(CommandAction),
}
@ -426,7 +442,8 @@ impl FormatDebug for ReturnValue {
fn fmt_debug(&self, f: &mut DebugFormatter, source: &str) -> fmt::Result {
match self {
Err(err) => write!(f, "{}", err.debug(source)),
Ok(ReturnSuccess::Value(v)) => write!(f, "{:?}", v.debug()),
Ok(ReturnSuccess::Value(v)) => write!(f, "{}", v.debug(source)),
Ok(ReturnSuccess::DebugValue(v)) => v.fmt_debug(f, source),
Ok(ReturnSuccess::Action(a)) => write!(f, "{}", a.debug(source)),
}
}
@ -447,6 +464,10 @@ impl ReturnSuccess {
Ok(ReturnSuccess::Value(input.into()))
}
pub fn debug_value(input: impl Into<Tagged<Value>>) -> ReturnValue {
Ok(ReturnSuccess::DebugValue(input.into()))
}
pub fn action(input: CommandAction) -> ReturnValue {
Ok(ReturnSuccess::Action(input))
}

View File

@ -32,7 +32,7 @@ pub fn debug(args: CommandArgs, _registry: &CommandRegistry) -> Result<OutputStr
Ok(input
.values
.map(|v| {
println!("{:?}", v);
outln!("{:?}", v);
ReturnSuccess::value(v)
})
.to_output_stream())

View File

@ -0,0 +1,42 @@
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
pub struct DebugValue;
#[derive(Deserialize)]
pub struct DebugArgs {}
impl WholeStreamCommand for DebugValue {
fn name(&self) -> &str {
"debug"
}
fn signature(&self) -> Signature {
Signature::build("debug")
}
fn usage(&self) -> &str {
"Print the Rust debug representation of the values"
}
fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
args.process(registry, debug_value)?.run()
}
}
fn debug_value(
_args: DebugArgs,
RunnableContext { mut input, .. }: RunnableContext,
) -> Result<impl ToOutputStream, ShellError> {
let stream = async_stream! {
while let Some(row) = input.values.next().await {
yield ReturnSuccess::debug_value(row.clone())
}
};
Ok(stream)
}

View File

@ -71,7 +71,11 @@ fn fetch(
) -> Box<dyn Fn(Tagged<Value>, Tag) -> Option<Tagged<Value>> + 'static> {
Box::new(move |value: Tagged<Value>, tag| match key {
Some(ref key_given) => {
if let Some(Tagged { item, .. }) = value.get_data_by_key(&key_given) {
if let Some(Tagged {
item,
tag: Tag { span, .. },
}) = value.get_data_by_key(key_given[..].spanned(tag.span))
{
Some(item.clone().tagged(tag))
} else {
None

View File

@ -10,7 +10,7 @@ pub(crate) fn format(input: Vec<Value>, host: &mut dyn Host) {
crate::format::print_view(&view, &mut *host);
if last != i {
println!("");
outln!("");
}
}
}

View File

@ -1,15 +1,17 @@
use crate::commands::WholeStreamCommand;
use crate::data::base::shape::Shapes;
use crate::data::Value;
use crate::errors::ShellError;
use crate::prelude::*;
use crate::utils::did_you_mean;
use crate::ColumnPath;
use futures_util::pin_mut;
use log::trace;
pub struct Get;
#[derive(Deserialize)]
pub struct GetArgs {
member: ColumnPath,
rest: Vec<ColumnPath>,
}
@ -19,13 +21,7 @@ impl WholeStreamCommand for Get {
}
fn signature(&self) -> Signature {
Signature::build("get")
.required(
"member",
SyntaxShape::ColumnPath,
"the path to the data to get",
)
.rest(
Signature::build("get").rest(
SyntaxShape::ColumnPath,
"optionally return additional data by path",
)
@ -44,8 +40,6 @@ impl WholeStreamCommand for Get {
}
}
pub type ColumnPath = Vec<Tagged<Value>>;
pub fn get_column_path(
path: &ColumnPath,
obj: &Tagged<Value>,
@ -53,85 +47,52 @@ pub fn get_column_path(
let fields = path.clone();
let value = obj.get_data_by_column_path(
obj.tag(),
path,
Box::new(move |(obj_source, column_path_tried)| {
Box::new(move |(obj_source, column_path_tried, error)| {
match obj_source {
Value::Table(rows) => {
let total = rows.len();
let end_tag = match fields.iter().nth_back(if fields.len() > 2 { 1 } else { 0 })
let end_tag = match fields
.members()
.iter()
.nth_back(if fields.members().len() > 2 { 1 } else { 0 })
{
Some(last_field) => last_field.tag(),
None => column_path_tried.tag(),
Some(last_field) => last_field.span(),
None => column_path_tried.span(),
};
return ShellError::labeled_error_with_secondary(
"Row not found",
format!(
"There isn't a row indexed at '{}'",
match &*column_path_tried {
Value::Primitive(primitive) => primitive.format(None),
_ => String::from(""),
}
),
column_path_tried.tag(),
format!("The table only has {} rows (0..{})", total, total - 1),
format!("There isn't a row indexed at {}", **column_path_tried),
column_path_tried.span(),
if total == 1 {
format!("The table only has 1 row")
} else {
format!("The table only has {} rows (0 to {})", total, total - 1)
},
end_tag,
);
}
_ => {}
}
match &column_path_tried {
Tagged {
item: Value::Primitive(Primitive::Int(index)),
..
} => {
return ShellError::labeled_error(
"No rows available",
format!(
"Not a table. Perhaps you meant to get the column '{}' instead?",
index
),
column_path_tried.tag(),
)
}
_ => match did_you_mean(&obj_source, &column_path_tried) {
match did_you_mean(&obj_source, column_path_tried) {
Some(suggestions) => {
return ShellError::labeled_error(
"Unknown column",
format!("did you mean '{}'?", suggestions[0].1),
tag_for_tagged_list(fields.iter().map(|p| p.tag())),
span_for_spanned_list(fields.members().iter().map(|p| p.span())),
)
}
None => {
return ShellError::labeled_error(
"Unknown column",
"row does not contain this column",
tag_for_tagged_list(fields.iter().map(|p| p.tag())),
)
}
},
None => {}
}
return error;
}),
);
let res = match value {
Ok(fetched) => match fetched {
Some(Tagged { item: v, .. }) => Ok((v.clone()).tagged(&obj.tag)),
None => match obj {
// If its None check for certain values.
Tagged {
item: Value::Primitive(Primitive::String(_)),
..
} => Ok(obj.clone()),
Tagged {
item: Value::Primitive(Primitive::Path(_)),
..
} => Ok(obj.clone()),
_ => Ok(Value::nothing().tagged(&obj.tag)),
},
},
Ok(Tagged { item: v, tag }) => Ok((v.clone()).tagged(&tag)),
Err(reason) => Err(reason),
};
@ -139,14 +100,33 @@ pub fn get_column_path(
}
pub fn get(
GetArgs {
member,
rest: fields,
}: GetArgs,
GetArgs { rest: mut fields }: GetArgs,
RunnableContext { input, .. }: RunnableContext,
) -> Result<OutputStream, ShellError> {
trace!("get {:?} {:?}", member, fields);
if fields.len() == 0 {
let stream = async_stream! {
let values = input.values;
pin_mut!(values);
let mut shapes = Shapes::new();
let mut index = 0;
while let Some(row) = values.next().await {
shapes.add(&row.item, index);
index += 1;
}
for row in shapes.to_values() {
yield ReturnSuccess::value(row);
}
};
let stream: BoxStream<'static, ReturnValue> = stream.boxed();
Ok(stream.to_output_stream())
} else {
let member = fields.remove(0);
trace!("get {:?} {:?}", member, fields);
let stream = input
.values
.map(move |item| {
@ -168,25 +148,21 @@ pub fn get(
item: Value::Table(rows),
..
} => {
for row in rows {
result.push_back(ReturnSuccess::value(
Tagged {
item: row.item,
tag: Tag::from(&item.tag),
}
.map_anchored(&item.tag.anchor),
))
for item in rows {
result.push_back(ReturnSuccess::value(item.clone()));
}
}
other => result
.push_back(ReturnSuccess::value((*other).clone().tagged(&item.tag))),
other => result.push_back(ReturnSuccess::value(other.clone())),
},
Err(reason) => result.push_back(Err(reason)),
Err(reason) => result
.push_back(ReturnSuccess::value(Value::Error(reason).tagged_unknown())),
}
}
result
})
.flatten();
Ok(stream.to_output_stream())
}
}

View File

@ -70,7 +70,7 @@ pub fn group(
let mut groups = indexmap::IndexMap::new();
for value in values {
let group_key = value.get_data_by_key(column_name);
let group_key = value.get_data_by_key(column_name.borrow_spanned());
if group_key.is_none() {
let possibilities = value.data_descriptors();

View File

@ -44,7 +44,11 @@ impl PerItemCommand for Help {
short_desc.insert("name", cmd);
short_desc.insert(
"description",
value.get_data_by_key("usage").unwrap().as_string().unwrap(),
value
.get_data_by_key("usage".spanned_unknown())
.unwrap()
.as_string()
.unwrap(),
);
help.push_back(ReturnSuccess::value(short_desc.into_tagged_value()));

View File

@ -88,7 +88,8 @@ pub fn histogram(
for percentage in start.into_iter() {
let mut fact = TaggedDictBuilder::new(&name);
fact.insert_tagged(&column, group_labels.get(idx).unwrap().clone());
let value: Tagged<String> = group_labels.get(idx).unwrap().clone();
fact.insert_tagged(&column, Value::string(value.item).tagged(value.tag));
if let Tagged { item: Value::Primitive(Primitive::Int(ref num)), .. } = percentage.clone() {
fact.insert(&frequency_column_name, std::iter::repeat("*").take(num.to_i32().unwrap() as usize).collect::<String>());

View File

@ -35,8 +35,6 @@ fn lines(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream,
let tag = args.name_tag();
let input = args.input;
let input: InputStream = trace_stream!(target: "nu::trace_stream::lines", "input" = input);
let stream = input
.values
.map(move |v| match v.item {

View File

@ -70,7 +70,7 @@ pub fn pivot(args: PivotArgs, context: RunnableContext) -> Result<OutputStream,
if args.header_row {
for i in input.clone() {
if let Some(desc) = descs.get(0) {
match i.get_data_by_key(&desc) {
match i.get_data_by_key(desc[..].spanned_unknown()) {
Some(x) => {
if let Ok(s) = x.as_string() {
headers.push(s);
@ -115,7 +115,7 @@ pub fn pivot(args: PivotArgs, context: RunnableContext) -> Result<OutputStream,
}
for i in input.clone() {
match i.get_data_by_key(&desc) {
match i.get_data_by_key(desc[..].spanned_unknown()) {
Some(x) => {
dict.insert_tagged(headers[column_num].clone(), x.clone());
}

View File

@ -237,7 +237,7 @@ pub fn filter_plugin(
Err(e) => {
let mut result = VecDeque::new();
result.push_back(Err(ShellError::untagged_runtime_error(format!(
"Error while processing filter response: {:?} {}",
"Error while processing filter response: {:?}\n== input ==\n{}",
e, input
))));
result

View File

@ -40,7 +40,7 @@ fn sort_by(
let calc_key = |item: &Tagged<Value>| {
rest.iter()
.map(|f| item.get_data_by_key(f).map(|i| i.clone()))
.map(|f| item.get_data_by_key(f.borrow_spanned()).map(|i| i.clone()))
.collect::<Vec<Option<Tagged<Value>>>>()
};
vec.sort_by_cached_key(calc_key);

View File

@ -108,7 +108,7 @@ pub fn split(
other => {
return Err(ShellError::type_error(
"a table value",
other.tagged_type_name(),
other.spanned_type_name(),
))
}
}
@ -117,7 +117,7 @@ pub fn split(
_ => {
return Err(ShellError::type_error(
"a table value",
group.tagged_type_name(),
group.spanned_type_name(),
))
}
}
@ -125,7 +125,7 @@ pub fn split(
ref other => {
return Err(ShellError::type_error(
"a table value",
other.tagged_type_name(),
other.spanned_type_name(),
))
}
}
@ -134,7 +134,7 @@ pub fn split(
_ => {
return Err(ShellError::type_error(
"a table value",
value.tagged_type_name(),
value.spanned_type_name(),
))
}
}

View File

@ -66,8 +66,8 @@ fn t_sort_by(
};
if show_columns {
for label in columns_sorted(column_grouped_by_name, &values[0], &name).iter() {
yield ReturnSuccess::value(label.clone());
for label in columns_sorted(column_grouped_by_name, &values[0], &name).into_iter() {
yield ReturnSuccess::value(Value::string(label.item).tagged(label.tag));
}
} else {
match t_sort(column_grouped_by_name, None, &values[0], name) {
@ -82,7 +82,7 @@ pub fn columns_sorted(
_group_by_name: Option<String>,
value: &Tagged<Value>,
tag: impl Into<Tag>,
) -> Vec<Tagged<Value>> {
) -> Vec<Tagged<String>> {
let origin_tag = tag.into();
match value {
@ -110,22 +110,20 @@ pub fn columns_sorted(
keys.sort();
let keys: Vec<Value> = keys
let keys: Vec<String> = keys
.into_iter()
.map(|k| {
Value::string(match k {
.map(|k| match k {
Tagged {
item: Value::Primitive(Primitive::Date(d)),
..
} => format!("{}", d.format("%B %d-%Y")),
_ => k.as_string().unwrap(),
})
})
.collect();
keys.into_iter().map(|k| k.tagged(&origin_tag)).collect()
}
_ => vec![Value::string("default").tagged(&origin_tag)],
_ => vec![format!("default").tagged(&origin_tag)],
}
}
@ -139,7 +137,8 @@ pub fn t_sort(
match group_by_name {
Some(column_name) => {
let sorted_labels = columns_sorted(Some(column_name), value, &origin_tag);
let sorted_labels: Vec<Tagged<String>> =
columns_sorted(Some(column_name), value, &origin_tag);
match split_by_name {
None => {
@ -147,70 +146,41 @@ pub fn t_sort(
dataset.insert_tagged("default", value.clone());
let dataset = dataset.into_tagged_value();
let split_labels = match &dataset {
let split_labels: Vec<Tagged<String>> = match &dataset {
Tagged {
item: Value::Row(rows),
..
} => {
let mut keys: Vec<Tagged<Value>> = rows
let mut keys: Vec<Tagged<String>> = rows
.entries
.keys()
.map(|s| s.as_ref())
.map(|k: &str| {
let date = NaiveDate::parse_from_str(k, "%B %d-%Y");
let date = match date {
Ok(parsed) => Value::Primitive(Primitive::Date(
DateTime::<Utc>::from_utc(
parsed.and_hms(12, 34, 56),
Utc,
),
)),
Err(_) => Value::string(k),
};
date.tagged_unknown()
})
.map(|k| k.clone().tagged_unknown())
.collect();
keys.sort();
let keys: Vec<Value> = keys
.into_iter()
.map(|k| {
Value::string(match k {
Tagged {
item: Value::Primitive(Primitive::Date(d)),
..
} => format!("{}", d.format("%B %d-%Y")),
_ => k.as_string().unwrap(),
})
})
.collect();
keys.into_iter().map(|k| k.tagged(&origin_tag)).collect()
keys
}
_ => vec![],
};
let results: Vec<Vec<Tagged<Value>>> = split_labels
.into_iter()
.iter()
.map(|split| {
let groups = dataset.get_data_by_key(&split.as_string().unwrap());
let groups = dataset.get_data_by_key(split.borrow_spanned());
sorted_labels
.clone()
.into_iter()
.map(|label| {
let label = label.as_string().unwrap();
match groups {
.map(|label| match &groups {
Some(Tagged {
item: Value::Row(dict),
..
}) => dict.get_data_by_key(&label).unwrap().clone(),
}) => dict
.get_data_by_key(label.borrow_spanned())
.unwrap()
.clone(),
_ => Value::Table(vec![]).tagged(&origin_tag),
}
})
.collect()
})
@ -299,9 +269,9 @@ mod tests {
Tag::unknown()
),
vec![
string("August 23-2019"),
string("September 24-2019"),
string("October 10-2019")
format!("August 23-2019").tagged_unknown(),
format!("September 24-2019").tagged_unknown(),
format!("October 10-2019").tagged_unknown()
]
)
}

View File

@ -1,6 +1,7 @@
use crate::commands::WholeStreamCommand;
use crate::data::{Dictionary, Primitive, Value};
use crate::prelude::*;
use crate::RawPathMember;
use bson::{encode_document, oid::ObjectId, spec::BinarySubtype, Bson, Document};
use std::convert::TryInto;
@ -51,6 +52,16 @@ pub fn value_to_bson_value(v: &Tagged<Value>) -> Result<Bson, ShellError> {
}
Value::Primitive(Primitive::Nothing) => Bson::Null,
Value::Primitive(Primitive::String(s)) => Bson::String(s.clone()),
Value::Primitive(Primitive::ColumnPath(path)) => Bson::Array(
path.iter()
.map(|x| match &x.item {
RawPathMember::String(string) => Ok(Bson::String(string.clone())),
RawPathMember::Int(int) => Ok(Bson::I64(
int.tagged(&v.tag).coerce_into("converting to BSON")?,
)),
})
.collect::<Result<Vec<Bson>, ShellError>>()?,
),
Value::Primitive(Primitive::Pattern(p)) => Bson::String(p.clone()),
Value::Primitive(Primitive::Path(s)) => Bson::String(s.display().to_string()),
Value::Table(l) => Bson::Array(
@ -177,7 +188,7 @@ fn get_binary_subtype<'a>(tagged_value: &'a Tagged<Value>) -> Result<BinarySubty
)),
_ => Err(ShellError::type_error(
"bson binary",
tagged_value.tagged_type_name(),
tagged_value.type_name().spanned(tagged_value.span()),
)),
}
}

View File

@ -1,6 +1,7 @@
use crate::data::{Primitive, Value};
use crate::prelude::*;
use csv::WriterBuilder;
use indexmap::{indexset, IndexSet};
fn from_value_to_delimited_string(
tagged_value: &Tagged<Value>,
@ -46,15 +47,16 @@ fn from_value_to_delimited_string(
.from_writer(vec![]);
let merged_descriptors = merge_descriptors(&list);
wtr.write_record(&merged_descriptors)
wtr.write_record(merged_descriptors.iter().map(|item| &item.item[..]))
.expect("can not write.");
for l in list {
let mut row = vec![];
for desc in &merged_descriptors {
match l.item.get_data_by_key(&desc) {
match l.item.get_data_by_key(desc.borrow_spanned()) {
Some(s) => {
row.push(to_string_tagged_value(s)?);
row.push(to_string_tagged_value(&s)?);
}
None => {
row.push(String::new());
@ -127,12 +129,14 @@ fn to_string_tagged_value(v: &Tagged<Value>) -> Result<String, ShellError> {
}
}
fn merge_descriptors(values: &[Tagged<Value>]) -> Vec<String> {
let mut ret = vec![];
fn merge_descriptors(values: &[Tagged<Value>]) -> Vec<Spanned<String>> {
let mut ret: Vec<Spanned<String>> = vec![];
let mut seen: IndexSet<String> = indexset! {};
for value in values {
for desc in value.data_descriptors() {
if !ret.contains(&desc) {
ret.push(desc);
if !seen.contains(&desc[..]) {
seen.insert(desc.clone());
ret.push(desc.spanned(value.tag.span));
}
}
}

View File

@ -1,6 +1,7 @@
use crate::commands::WholeStreamCommand;
use crate::data::{Primitive, Value};
use crate::prelude::*;
use crate::RawPathMember;
pub struct ToJSON;
@ -50,6 +51,19 @@ pub fn value_to_json_value(v: &Tagged<Value>) -> Result<serde_json::Value, Shell
Value::Primitive(Primitive::Nothing) => serde_json::Value::Null,
Value::Primitive(Primitive::Pattern(s)) => serde_json::Value::String(s.clone()),
Value::Primitive(Primitive::String(s)) => serde_json::Value::String(s.clone()),
Value::Primitive(Primitive::ColumnPath(path)) => serde_json::Value::Array(
path.iter()
.map(|x| match &x.item {
RawPathMember::String(string) => Ok(serde_json::Value::String(string.clone())),
RawPathMember::Int(int) => Ok(serde_json::Value::Number(
serde_json::Number::from(CoerceInto::<i64>::coerce_into(
int.tagged(&v.tag),
"converting to JSON number",
)?),
)),
})
.collect::<Result<Vec<serde_json::Value>, ShellError>>()?,
),
Value::Primitive(Primitive::Path(s)) => serde_json::Value::String(s.display().to_string()),
Value::Table(l) => serde_json::Value::Array(json_list(l)?),

View File

@ -98,8 +98,9 @@ fn nu_value_to_sqlite_string(v: Value) -> String {
Primitive::Date(d) => format!("'{}'", d),
Primitive::Path(p) => format!("'{}'", p.display().to_string().replace("'", "''")),
Primitive::Binary(u) => format!("x'{}'", encode(u)),
Primitive::BeginningOfStream => "NULL".into(),
Primitive::EndOfStream => "NULL".into(),
Primitive::BeginningOfStream | Primitive::EndOfStream | Primitive::ColumnPath(_) => {
"NULL".into()
}
},
_ => "NULL".into(),
}
@ -179,9 +180,9 @@ fn sqlite_input_stream_to_bytes(
{
Ok(_) => (),
Err(e) => {
println!("{}", create);
println!("{}", insert);
println!("{:?}", e);
outln!("{}", create);
outln!("{}", insert);
outln!("{:?}", e);
return Err(std::io::Error::new(std::io::ErrorKind::Other, e));
}
}

View File

@ -1,6 +1,7 @@
use crate::commands::WholeStreamCommand;
use crate::data::{Primitive, Value};
use crate::prelude::*;
use crate::RawPathMember;
pub struct ToTOML;
@ -48,6 +49,17 @@ pub fn value_to_toml_value(v: &Tagged<Value>) -> Result<toml::Value, ShellError>
Value::Primitive(Primitive::Pattern(s)) => toml::Value::String(s.clone()),
Value::Primitive(Primitive::String(s)) => toml::Value::String(s.clone()),
Value::Primitive(Primitive::Path(s)) => toml::Value::String(s.display().to_string()),
Value::Primitive(Primitive::ColumnPath(path)) => toml::Value::Array(
path.iter()
.map(|x| match &x.item {
RawPathMember::String(string) => Ok(toml::Value::String(string.clone())),
RawPathMember::Int(int) => Ok(toml::Value::Integer(
int.tagged(&v.tag)
.coerce_into("converting to TOML integer")?,
)),
})
.collect::<Result<Vec<toml::Value>, ShellError>>()?,
),
Value::Table(l) => toml::Value::Array(collect_values(l)?),
Value::Error(e) => return Err(e.clone()),

View File

@ -1,6 +1,7 @@
use crate::commands::WholeStreamCommand;
use crate::data::{Primitive, Value};
use crate::prelude::*;
use crate::RawPathMember;
pub struct ToYAML;
@ -47,6 +48,25 @@ pub fn value_to_yaml_value(v: &Tagged<Value>) -> Result<serde_yaml::Value, Shell
Value::Primitive(Primitive::Nothing) => serde_yaml::Value::Null,
Value::Primitive(Primitive::Pattern(s)) => serde_yaml::Value::String(s.clone()),
Value::Primitive(Primitive::String(s)) => serde_yaml::Value::String(s.clone()),
Value::Primitive(Primitive::ColumnPath(path)) => {
let mut out = vec![];
for member in path.iter() {
match &member.item {
RawPathMember::String(string) => {
out.push(serde_yaml::Value::String(string.clone()))
}
RawPathMember::Int(int) => out.push(serde_yaml::Value::Number(
serde_yaml::Number::from(CoerceInto::<i64>::coerce_into(
int.tagged(&member.span),
"converting to YAML number",
)?),
)),
}
}
serde_yaml::Value::Sequence(out)
}
Value::Primitive(Primitive::Path(s)) => serde_yaml::Value::String(s.display().to_string()),
Value::Table(l) => {

52
src/commands/what.rs Normal file
View File

@ -0,0 +1,52 @@
use crate::commands::WholeStreamCommand;
use crate::data::Value;
use crate::errors::ShellError;
use crate::prelude::*;
use futures::StreamExt;
use futures_util::pin_mut;
pub struct What;
#[derive(Deserialize)]
pub struct WhatArgs {}
impl WholeStreamCommand for What {
fn name(&self) -> &str {
"what?"
}
fn signature(&self) -> Signature {
Signature::build("what?")
}
fn usage(&self) -> &str {
"Describes the objects in the stream."
}
fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
args.process(registry, what)?.run()
}
}
pub fn what(
WhatArgs {}: WhatArgs,
RunnableContext { input, host, .. }: RunnableContext,
) -> Result<OutputStream, ShellError> {
let stream = async_stream! {
let values = input.values;
pin_mut!(values);
while let Some(row) = values.next().await {
let name = row.format_type(host.clone().lock().unwrap().width());
yield ReturnSuccess::value(Value::string(name).tagged(Tag::unknown_anchor(row.tag.span)));
}
};
let stream: BoxStream<'static, ReturnValue> = stream.boxed();
Ok(OutputStream::from(stream))
}

View File

@ -64,7 +64,8 @@ impl CommandRegistry {
#[derive(Clone)]
pub struct Context {
registry: CommandRegistry,
host: Arc<Mutex<dyn Host + Send>>,
host: Arc<Mutex<Box<dyn Host>>>,
pub current_errors: Arc<Mutex<Vec<ShellError>>>,
pub ctrl_c: Arc<AtomicBool>,
pub(crate) shell_manager: ShellManager,
}
@ -85,18 +86,69 @@ impl Context {
let registry = CommandRegistry::new();
Ok(Context {
registry: registry.clone(),
host: Arc::new(Mutex::new(crate::env::host::BasicHost)),
host: Arc::new(Mutex::new(Box::new(crate::env::host::BasicHost))),
current_errors: Arc::new(Mutex::new(vec![])),
ctrl_c: Arc::new(AtomicBool::new(false)),
shell_manager: ShellManager::basic(registry)?,
})
}
pub(crate) fn with_host(&mut self, block: impl FnOnce(&mut dyn Host)) {
pub(crate) fn error(&mut self, error: ShellError) {
self.with_errors(|errors| errors.push(error))
}
pub(crate) fn maybe_print_errors(&mut self, source: Text) -> bool {
let errors = self.current_errors.clone();
let errors = errors.lock();
let host = self.host.clone();
let host = host.lock();
let result: bool;
match (errors, host) {
(Err(err), _) => {
errln!(
"Unexpected error attempting to acquire the lock of the current errors: {:?}",
err
);
result = false;
}
(_, Err(err)) => {
errln!(
"Unexpected error attempting to acquire the lock of the current errors: {:?}",
err
);
result = false;
}
(Ok(mut errors), Ok(host)) => {
if errors.len() > 0 {
let error = errors[0].clone();
*errors = vec![];
crate::cli::print_err(error, &*host, &source);
result = true;
} else {
result = false;
}
}
};
result
}
pub(crate) fn with_host<T>(&mut self, block: impl FnOnce(&mut dyn Host) -> T) -> T {
let mut host = self.host.lock().unwrap();
block(&mut *host)
}
pub(crate) fn with_errors<T>(&mut self, block: impl FnOnce(&mut Vec<ShellError>) -> T) -> T {
let mut errors = self.current_errors.lock().unwrap();
block(&mut *errors)
}
pub fn add_commands(&mut self, commands: Vec<Arc<Command>>) {
for command in commands {
self.registry.insert(command.name().to_string(), command);

View File

@ -1,7 +1,13 @@
mod debug;
mod property_get;
pub(crate) mod shape;
use crate::context::CommandRegistry;
use crate::data::base::shape::{Column, InlineShape, TypeShape};
use crate::data::TaggedDictBuilder;
use crate::errors::ShellError;
use crate::evaluate::{evaluate_baseline_expr, Scope};
use crate::parser::hir::path::{ColumnPath, PathMember};
use crate::parser::{hir, Operator};
use crate::prelude::*;
use crate::Text;
@ -11,7 +17,6 @@ use derive_new::new;
use indexmap::IndexMap;
use log::trace;
use serde::{Deserialize, Serialize};
use std::fmt;
use std::path::PathBuf;
use std::time::SystemTime;
@ -76,6 +81,7 @@ pub enum Primitive {
Decimal(BigDecimal),
Bytes(u64),
String(String),
ColumnPath(ColumnPath),
Pattern(String),
Boolean(bool),
Date(DateTime<Utc>),
@ -89,6 +95,27 @@ pub enum Primitive {
EndOfStream,
}
impl ShellTypeName for Primitive {
fn type_name(&self) -> &'static str {
match self {
Primitive::Nothing => "nothing",
Primitive::Int(_) => "integer",
Primitive::Decimal(_) => "decimal",
Primitive::Bytes(_) => "bytes",
Primitive::String(_) => "string",
Primitive::ColumnPath(_) => "column path",
Primitive::Pattern(_) => "pattern",
Primitive::Boolean(_) => "boolean",
Primitive::Date(_) => "date",
Primitive::Duration(_) => "duration",
Primitive::Path(_) => "file path",
Primitive::Binary(_) => "binary",
Primitive::BeginningOfStream => "marker<beginning of stream>",
Primitive::EndOfStream => "marker<end of stream>",
}
}
}
impl From<BigDecimal> for Primitive {
fn from(decimal: BigDecimal) -> Primitive {
Primitive::Decimal(decimal)
@ -102,47 +129,6 @@ impl From<f64> for Primitive {
}
impl Primitive {
pub(crate) fn type_name(&self) -> String {
use Primitive::*;
match self {
Nothing => "nothing",
BeginningOfStream => "beginning-of-stream",
EndOfStream => "end-of-stream",
Path(_) => "path",
Int(_) => "int",
Decimal(_) => "decimal",
Duration(_) => "duration",
Bytes(_) => "bytes",
Pattern(_) => "pattern",
String(_) => "string",
Boolean(_) => "boolean",
Date(_) => "date",
Binary(_) => "binary",
}
.to_string()
}
pub(crate) fn debug(&self, f: &mut fmt::Formatter) -> fmt::Result {
use Primitive::*;
match self {
Nothing => write!(f, "Nothing"),
BeginningOfStream => write!(f, "BeginningOfStream"),
EndOfStream => write!(f, "EndOfStream"),
Int(int) => write!(f, "{}", int),
Path(path) => write!(f, "{}", path.display()),
Decimal(decimal) => write!(f, "{}", decimal),
Duration(secs) => write!(f, "{}", secs),
Bytes(bytes) => write!(f, "{}", bytes),
Pattern(string) => write!(f, "{:?}", string),
String(string) => write!(f, "{:?}", string),
Boolean(boolean) => write!(f, "{}", boolean),
Date(date) => write!(f, "{}", date),
Binary(binary) => write!(f, "{:?}", binary),
}
}
pub fn number(number: impl Into<Number>) -> Primitive {
let number = number.into();
@ -177,6 +163,24 @@ impl Primitive {
Primitive::Decimal(decimal) => format!("{}", decimal),
Primitive::Pattern(s) => format!("{}", s),
Primitive::String(s) => format!("{}", s),
Primitive::ColumnPath(p) => {
let mut members = p.iter();
let mut f = String::new();
f.push_str(
&members
.next()
.expect("BUG: column path with zero members")
.to_string(),
);
for member in members {
f.push_str(".");
f.push_str(&member.to_string())
}
f
}
Primitive::Boolean(b) => match (b, field_name) {
(true, None) => format!("Yes"),
(false, None) => format!("No"),
@ -220,7 +224,7 @@ pub struct Operation {
pub(crate) right: Value,
}
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Clone, Serialize, Deserialize, new)]
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Clone, Hash, Serialize, Deserialize, new)]
pub struct Block {
pub(crate) expressions: Vec<hir::Expression>,
pub(crate) source: Text,
@ -270,6 +274,18 @@ pub enum Value {
Block(Block),
}
impl ShellTypeName for Value {
fn type_name(&self) -> &'static str {
match self {
Value::Primitive(p) => p.type_name(),
Value::Row(_) => "row",
Value::Table(_) => "table",
Value::Error(_) => "error",
Value::Block(_) => "block",
}
}
}
impl Into<Value> for Number {
fn into(self) -> Value {
match self {
@ -288,41 +304,16 @@ impl Into<Value> for &Number {
}
}
pub fn debug_list(values: &Vec<Tagged<Value>>) -> ValuesDebug<'_> {
ValuesDebug { values }
}
pub struct ValuesDebug<'a> {
values: &'a Vec<Tagged<Value>>,
}
impl fmt::Debug for ValuesDebug<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_list()
.entries(self.values.iter().map(|i| i.debug()))
.finish()
}
}
pub struct ValueDebug<'a> {
value: &'a Tagged<Value>,
}
impl fmt::Debug for ValueDebug<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.value.item() {
Value::Primitive(p) => p.debug(f),
Value::Row(o) => o.debug(f),
Value::Table(l) => debug_list(l).fmt(f),
Value::Block(_) => write!(f, "[[block]]"),
Value::Error(_) => write!(f, "[[error]]"),
}
}
}
impl Tagged<Value> {
pub fn tagged_type_name(&self) -> Tagged<String> {
let name = self.type_name();
let name = self.type_name().to_string();
name.tagged(self.tag())
}
}
impl Tagged<&Value> {
pub fn tagged_type_name(&self) -> Tagged<String> {
let name = self.type_name().to_string();
name.tagged(self.tag())
}
}
@ -335,7 +326,7 @@ impl std::convert::TryFrom<&Tagged<Value>> for Block {
Value::Block(block) => Ok(block.clone()),
v => Err(ShellError::type_error(
"Block",
v.type_name().tagged(value.tag()),
v.type_name().spanned(value.span()),
)),
}
}
@ -351,7 +342,7 @@ impl std::convert::TryFrom<&Tagged<Value>> for i64 {
}
v => Err(ShellError::type_error(
"Integer",
v.type_name().tagged(value.tag()),
v.type_name().spanned(value.span()),
)),
}
}
@ -365,7 +356,7 @@ impl std::convert::TryFrom<&Tagged<Value>> for String {
Value::Primitive(Primitive::String(s)) => Ok(s.clone()),
v => Err(ShellError::type_error(
"String",
v.type_name().tagged(value.tag()),
v.type_name().spanned(value.span()),
)),
}
}
@ -379,7 +370,7 @@ impl std::convert::TryFrom<&Tagged<Value>> for Vec<u8> {
Value::Primitive(Primitive::Binary(b)) => Ok(b.clone()),
v => Err(ShellError::type_error(
"Binary",
v.type_name().tagged(value.tag()),
v.type_name().spanned(value.span()),
)),
}
}
@ -393,7 +384,7 @@ impl<'a> std::convert::TryFrom<&'a Tagged<Value>> for &'a crate::data::Dictionar
Value::Row(d) => Ok(d),
v => Err(ShellError::type_error(
"Dictionary",
v.type_name().tagged(value.tag()),
v.type_name().spanned(value.span()),
)),
}
}
@ -415,61 +406,14 @@ impl std::convert::TryFrom<Option<&Tagged<Value>>> for Switch {
Value::Primitive(Primitive::Boolean(true)) => Ok(Switch::Present),
v => Err(ShellError::type_error(
"Boolean",
v.type_name().tagged(value.tag()),
v.type_name().spanned(value.span()),
)),
},
}
}
}
impl Tagged<Value> {
pub(crate) fn debug(&self) -> ValueDebug<'_> {
ValueDebug { value: self }
}
pub fn as_column_path(&self) -> Result<Tagged<Vec<Tagged<Value>>>, ShellError> {
match &self.item {
Value::Primitive(Primitive::String(s)) => {
Ok(vec![Value::string(s).tagged(&self.tag)].tagged(&self.tag))
}
Value::Table(table) => Ok(table.to_vec().tagged(&self.tag)),
other => Err(ShellError::type_error(
"column name",
other.type_name().tagged(&self.tag),
)),
}
}
pub fn as_string(&self) -> Result<String, ShellError> {
match &self.item {
Value::Primitive(Primitive::String(s)) => Ok(s.clone()),
Value::Primitive(Primitive::Boolean(x)) => Ok(format!("{}", x)),
Value::Primitive(Primitive::Decimal(x)) => Ok(format!("{}", x)),
Value::Primitive(Primitive::Int(x)) => Ok(format!("{}", x)),
Value::Primitive(Primitive::Bytes(x)) => Ok(format!("{}", x)),
Value::Primitive(Primitive::Path(x)) => Ok(format!("{}", x.display())),
Value::Primitive(Primitive::Date(x)) => Ok(format!("{}", x.to_rfc3339())),
// TODO: this should definitely be more general with better errors
other => Err(ShellError::labeled_error(
"Expected string",
other.type_name(),
&self.tag,
)),
}
}
}
impl Value {
pub fn type_name(&self) -> String {
match self {
Value::Primitive(p) => p.type_name(),
Value::Row(_) => format!("row"),
Value::Table(_) => format!("table"),
Value::Block(_) => format!("block"),
Value::Error(_) => format!("error"),
}
}
pub fn data_descriptors(&self) -> Vec<String> {
match self {
Value::Primitive(_) => vec![],
@ -485,201 +429,6 @@ impl Value {
}
}
pub(crate) fn get_data_by_index(&self, idx: usize) -> Option<&Tagged<Value>> {
match self {
Value::Table(value_set) => value_set.get(idx),
_ => None,
}
}
pub(crate) fn get_data_by_key(&self, name: &str) -> Option<&Tagged<Value>> {
match self {
Value::Row(o) => o.get_data_by_key(name),
Value::Table(l) => {
for item in l {
match item {
Tagged {
item: Value::Row(o),
..
} => match o.get_data_by_key(name) {
Some(v) => return Some(v),
None => {}
},
_ => {}
}
}
None
}
_ => None,
}
}
pub(crate) fn get_mut_data_by_key(&mut self, name: &str) -> Option<&mut Tagged<Value>> {
match self {
Value::Row(ref mut o) => o.get_mut_data_by_key(name),
Value::Table(ref mut l) => {
for item in l {
match item {
Tagged {
item: Value::Row(ref mut o),
..
} => match o.get_mut_data_by_key(name) {
Some(v) => return Some(v),
None => {}
},
_ => {}
}
}
None
}
_ => None,
}
}
pub fn get_data_by_column_path(
&self,
tag: Tag,
path: &Vec<Tagged<Value>>,
callback: Box<dyn FnOnce((Value, Tagged<Value>)) -> ShellError>,
) -> Result<Option<Tagged<&Value>>, ShellError> {
let mut column_path = vec![];
for value in path {
column_path.push(
Value::string(value.as_string().unwrap_or("".to_string())).tagged(&value.tag),
);
}
let path = column_path;
let mut current = self;
for p in path {
let value = p.as_string().unwrap_or("".to_string());
let value = match value.parse::<usize>() {
Ok(number) => match current {
Value::Table(_) => current.get_data_by_index(number),
Value::Row(_) => current.get_data_by_key(&value),
_ => None,
},
Err(_) => match self {
Value::Table(_) | Value::Row(_) => current.get_data_by_key(&value),
_ => None,
},
};
match value {
Some(v) => current = v,
None => return Err(callback((current.clone(), p.clone()))),
}
}
Ok(Some(current.tagged(tag)))
}
pub fn insert_data_at_column_path(
&self,
tag: Tag,
split_path: &Vec<Tagged<Value>>,
new_value: Value,
) -> Option<Tagged<Value>> {
let split_path = split_path
.into_iter()
.map(|p| match p {
Tagged {
item: Value::Primitive(Primitive::String(s)),
tag,
} => Ok(s.clone().tagged(tag)),
o => Err(o),
})
.filter_map(Result::ok)
.collect::<Vec<Tagged<String>>>();
let mut new_obj = self.clone();
if let Value::Row(ref mut o) = new_obj {
let mut current = o;
if split_path.len() == 1 {
// Special case for inserting at the top level
current
.entries
.insert(split_path[0].item.clone(), new_value.tagged(&tag));
return Some(new_obj.tagged(&tag));
}
for idx in 0..split_path.len() {
match current.entries.get_mut(&split_path[idx].item) {
Some(next) => {
if idx == (split_path.len() - 2) {
match &mut next.item {
Value::Row(o) => {
o.entries.insert(
split_path[idx + 1].to_string(),
new_value.tagged(&tag),
);
}
_ => {}
}
return Some(new_obj.tagged(&tag));
} else {
match next.item {
Value::Row(ref mut o) => {
current = o;
}
_ => return None,
}
}
}
_ => return None,
}
}
}
None
}
pub fn replace_data_at_column_path(
&self,
tag: Tag,
split_path: &Vec<Tagged<Value>>,
replaced_value: Value,
) -> Option<Tagged<Value>> {
let split_path = split_path
.into_iter()
.map(|p| match p {
Tagged {
item: Value::Primitive(Primitive::String(s)),
tag,
} => Ok(s.clone().tagged(tag)),
o => Err(o),
})
.filter_map(Result::ok)
.collect::<Vec<Tagged<String>>>();
let mut new_obj = self.clone();
let mut current = &mut new_obj;
for idx in 0..split_path.len() {
match current.get_mut_data_by_key(&split_path[idx].item) {
Some(next) => {
if idx == (split_path.len() - 1) {
*next = replaced_value.tagged(&tag);
return Some(new_obj.tagged(&tag));
} else {
current = &mut next.item;
}
}
None => {
return None;
}
}
}
None
}
pub fn get_data(&self, desc: &String) -> MaybeOwned<'_, Value> {
match self {
p @ Value::Primitive(_) => MaybeOwned::Borrowed(p),
@ -690,23 +439,18 @@ impl Value {
}
}
pub(crate) fn format_leaf(&self, desc: Option<&String>) -> String {
match self {
Value::Primitive(p) => p.format(desc),
Value::Block(b) => itertools::join(
b.expressions
.iter()
.map(|e| e.span.slice(&b.source).to_string()),
"; ",
),
Value::Row(_) => format!("[table: 1 row]"),
Value::Table(l) => format!(
"[table: {} {}]",
l.len(),
if l.len() == 1 { "row" } else { "rows" }
),
Value::Error(_) => format!("[error]"),
pub(crate) fn format_type(&self, width: usize) -> String {
TypeShape::from_value(self).colored_string(width)
}
pub(crate) fn format_leaf(&self) -> DebugDocBuilder {
InlineShape::from_value(self).format().pretty_debug()
}
pub(crate) fn format_for_column(&self, column: impl Into<Column>) -> DebugDocBuilder {
InlineShape::from_value(self)
.format_for_column(column)
.pretty_debug()
}
pub(crate) fn style_leaf(&self) -> &'static str {
@ -720,7 +464,7 @@ impl Value {
&self,
operator: &Operator,
other: &Value,
) -> Result<bool, (String, String)> {
) -> Result<bool, (&'static str, &'static str)> {
match operator {
_ => {
let coerced = coerce_compare(self, other)?;
@ -753,6 +497,20 @@ impl Value {
}
}
pub(crate) fn is_error(&self) -> bool {
match self {
Value::Error(_err) => true,
_ => false,
}
}
pub(crate) fn expect_error(&self) -> ShellError {
match self {
Value::Error(err) => err.clone(),
_ => panic!("Don't call expect_error without first calling is_error"),
}
}
#[allow(unused)]
pub fn row(entries: IndexMap<String, Tagged<Value>>) -> Value {
Value::Row(entries.into())
@ -766,6 +524,16 @@ impl Value {
Value::Primitive(Primitive::String(s.into()))
}
pub fn column_path(s: Vec<impl Into<PathMember>>) -> Value {
Value::Primitive(Primitive::ColumnPath(ColumnPath::new(
s.into_iter().map(|p| p.into()).collect(),
)))
}
pub fn int(i: impl Into<BigInt>) -> Value {
Value::Primitive(Primitive::Int(i.into()))
}
pub fn pattern(s: impl Into<String>) -> Value {
Value::Primitive(Primitive::String(s.into()))
}
@ -778,10 +546,6 @@ impl Value {
Value::Primitive(Primitive::Bytes(s.into()))
}
pub fn int(s: impl Into<BigInt>) -> Value {
Value::Primitive(Primitive::Int(s.into()))
}
pub fn decimal(s: impl Into<BigDecimal>) -> Value {
Value::Primitive(Primitive::Decimal(s.into()))
}
@ -837,7 +601,7 @@ impl Tagged<Value> {
Value::Primitive(Primitive::String(path_str)) => Ok(PathBuf::from(&path_str).clone()),
other => Err(ShellError::type_error(
"Path",
other.type_name().tagged(self.tag()),
other.type_name().spanned(self.span()),
)),
}
}
@ -900,7 +664,10 @@ impl CompareValues {
}
}
fn coerce_compare(left: &Value, right: &Value) -> Result<CompareValues, (String, String)> {
fn coerce_compare(
left: &Value,
right: &Value,
) -> Result<CompareValues, (&'static str, &'static str)> {
match (left, right) {
(Value::Primitive(left), Value::Primitive(right)) => coerce_compare_primitive(left, right),
@ -911,7 +678,7 @@ fn coerce_compare(left: &Value, right: &Value) -> Result<CompareValues, (String,
fn coerce_compare_primitive(
left: &Primitive,
right: &Primitive,
) -> Result<CompareValues, (String, String)> {
) -> Result<CompareValues, (&'static str, &'static str)> {
use Primitive::*;
Ok(match (left, right) {
@ -941,16 +708,19 @@ fn coerce_compare_primitive(
mod tests {
use crate::data::meta::*;
use crate::parser::hir::path::PathMember;
use crate::ColumnPath as ColumnPathValue;
use crate::ShellError;
use crate::Value;
use indexmap::IndexMap;
use num_bigint::BigInt;
fn string(input: impl Into<String>) -> Tagged<Value> {
Value::string(input.into()).tagged_unknown()
}
fn number(n: i64) -> Tagged<Value> {
Value::number(n).tagged_unknown()
fn int(input: impl Into<BigInt>) -> Tagged<Value> {
Value::int(input.into()).tagged_unknown()
}
fn row(entries: IndexMap<String, Tagged<Value>>) -> Tagged<Value> {
@ -961,12 +731,16 @@ mod tests {
Value::table(list).tagged_unknown()
}
fn error_callback() -> impl FnOnce((Value, Tagged<Value>)) -> ShellError {
move |(_obj_source, _column_path_tried)| ShellError::unimplemented("will never be called.")
fn error_callback(
reason: &'static str,
) -> impl FnOnce((&Value, &PathMember, ShellError)) -> ShellError {
move |(_obj_source, _column_path_tried, _err)| ShellError::unimplemented(reason)
}
fn column_path(paths: &Vec<Tagged<Value>>) -> Vec<Tagged<Value>> {
table(paths).as_column_path().unwrap().item
fn column_path(paths: &Vec<Tagged<Value>>) -> Tagged<ColumnPathValue> {
table(&paths.iter().cloned().collect())
.as_column_path()
.unwrap()
}
#[test]
@ -976,7 +750,7 @@ mod tests {
});
assert_eq!(
*row.get_data_by_key("amigos").unwrap(),
row.get_data_by_key("amigos".spanned_unknown()).unwrap(),
table(&vec![
string("andres"),
string("jonathan"),
@ -1000,17 +774,17 @@ mod tests {
});
assert_eq!(
**value
.get_data_by_column_path(tag, &field_path, Box::new(error_callback()))
.unwrap()
*value
.tagged(tag)
.get_data_by_column_path(&field_path, Box::new(error_callback("package.version")))
.unwrap(),
version
)
}
#[test]
fn column_path_that_contains_just_a_number_gets_a_row_from_a_table() {
let field_path = column_path(&vec![string("package"), string("authors"), number(0)]);
fn gets_first_matching_field_from_rows_with_same_field_inside_a_table() {
let field_path = column_path(&vec![string("package"), string("authors"), string("name")]);
let (_, tag) = string("Andrés N. Robalino").into_parts();
@ -1027,9 +801,43 @@ mod tests {
});
assert_eq!(
**value
.get_data_by_column_path(tag, &field_path, Box::new(error_callback()))
.unwrap()
value
.tagged(tag)
.get_data_by_column_path(
&field_path,
Box::new(error_callback("package.authors.name"))
)
.unwrap(),
table(&vec![
string("Andrés N. Robalino"),
string("Jonathan Turner"),
string("Yehuda Katz")
])
)
}
#[test]
fn column_path_that_contains_just_a_number_gets_a_row_from_a_table() {
let field_path = column_path(&vec![string("package"), string("authors"), int(0)]);
let (_, tag) = string("Andrés N. Robalino").into_parts();
let value = Value::row(indexmap! {
"package".into() => row(indexmap! {
"name".into() => string("nu"),
"version".into() => string("0.4.0"),
"authors".into() => table(&vec![
row(indexmap!{"name".into() => string("Andrés N. Robalino")}),
row(indexmap!{"name".into() => string("Jonathan Turner")}),
row(indexmap!{"name".into() => string("Yehuda Katz")})
])
})
});
assert_eq!(
*value
.tagged(tag)
.get_data_by_column_path(&field_path, Box::new(error_callback("package.authors.0")))
.unwrap(),
Value::row(indexmap! {
"name".into() => string("Andrés N. Robalino")
@ -1056,9 +864,12 @@ mod tests {
});
assert_eq!(
**value
.get_data_by_column_path(tag, &field_path, Box::new(error_callback()))
.unwrap()
*value
.tagged(tag)
.get_data_by_column_path(
&field_path,
Box::new(error_callback("package.authors.\"0\""))
)
.unwrap(),
Value::row(indexmap! {
"name".into() => string("Andrés N. Robalino")
@ -1081,7 +892,8 @@ mod tests {
let (replacement, tag) = string("jonas").into_parts();
let actual = sample
.replace_data_at_column_path(tag, &field_path, replacement)
.tagged(tag)
.replace_data_at_column_path(&field_path, replacement)
.unwrap();
assert_eq!(actual, row(indexmap! {"amigos".into() => string("jonas")}));
@ -1108,7 +920,8 @@ mod tests {
let (replacement, tag) = table(&vec![string("yehuda::jonathan::andres")]).into_parts();
let actual = sample
.replace_data_at_column_path(tag.clone(), &field_path, replacement.clone())
.tagged(tag.clone())
.replace_data_at_column_path(&field_path, replacement.clone())
.unwrap();
assert_eq!(
@ -1159,7 +972,8 @@ mod tests {
.into_parts();
let actual = sample
.replace_data_at_column_path(tag.clone(), &field_path, replacement.clone())
.tagged(tag.clone())
.replace_data_at_column_path(&field_path, replacement.clone())
.unwrap();
assert_eq!(

129
src/data/base/debug.rs Normal file
View File

@ -0,0 +1,129 @@
use crate::data::base::Primitive;
use crate::prelude::*;
use crate::traits::DebugDocBuilder as b;
use pretty::{BoxAllocator, DocAllocator};
use std::fmt;
impl FormatDebug for Tagged<Value> {
fn fmt_debug(&self, f: &mut DebugFormatter, source: &str) -> fmt::Result {
match &self.item {
Value::Primitive(p) => p.fmt_debug(f, source),
Value::Row(row) => f.say_dict(
"row",
row.entries()
.iter()
.map(|(key, value)| (&key[..], format!("{}", value.debug(source))))
.collect(),
),
Value::Table(table) => f.say_list(
"table",
table,
|f| write!(f, "["),
|f, item| write!(f, "{}", item.debug(source)),
|f| write!(f, " "),
|f| write!(f, "]"),
),
Value::Error(_) => f.say_simple("error"),
Value::Block(_) => f.say_simple("block"),
}
}
}
impl FormatDebug for Primitive {
fn fmt_debug(&self, f: &mut DebugFormatter, _source: &str) -> fmt::Result {
match self {
Primitive::Nothing => write!(f, "Nothing"),
Primitive::BeginningOfStream => write!(f, "BeginningOfStream"),
Primitive::EndOfStream => write!(f, "EndOfStream"),
Primitive::Int(int) => write!(f, "{}", int),
Primitive::Duration(duration) => write!(f, "{} seconds", *duration),
Primitive::Path(path) => write!(f, "{}", path.display()),
Primitive::Decimal(decimal) => write!(f, "{}", decimal),
Primitive::Bytes(bytes) => write!(f, "{}", bytes),
Primitive::Pattern(string) => write!(f, "{:?}", string),
Primitive::String(string) => write!(f, "{:?}", string),
Primitive::ColumnPath(path) => write!(f, "{:?}", path),
Primitive::Boolean(boolean) => write!(f, "{}", boolean),
Primitive::Date(date) => write!(f, "{}", date),
Primitive::Binary(binary) => write!(f, "{:?}", binary),
}
}
}
impl PrettyType for Primitive {
fn pretty_type(&self) -> DebugDocBuilder {
match self {
Primitive::Nothing => ty("nothing"),
Primitive::Int(_) => ty("integer"),
Primitive::Decimal(_) => ty("decimal"),
Primitive::Bytes(_) => ty("bytesize"),
Primitive::String(_) => ty("string"),
Primitive::ColumnPath(_) => ty("column-path"),
Primitive::Pattern(_) => ty("pattern"),
Primitive::Boolean(_) => ty("boolean"),
Primitive::Date(_) => ty("date"),
Primitive::Duration(_) => ty("duration"),
Primitive::Path(_) => ty("path"),
Primitive::Binary(_) => ty("binary"),
Primitive::BeginningOfStream => b::keyword("beginning-of-stream"),
Primitive::EndOfStream => b::keyword("end-of-stream"),
}
}
}
impl PrettyDebug for Primitive {
fn pretty_debug(&self) -> DebugDocBuilder {
match self {
Primitive::Nothing => b::primitive("nothing"),
Primitive::Int(int) => prim(format_args!("{}", int)),
Primitive::Decimal(decimal) => prim(format_args!("{}", decimal)),
Primitive::Bytes(bytes) => primitive_doc(bytes, "bytesize"),
Primitive::String(string) => prim(string),
Primitive::ColumnPath(path) => path.pretty_debug(),
Primitive::Pattern(pattern) => primitive_doc(pattern, "pattern"),
Primitive::Boolean(boolean) => match boolean {
true => b::primitive("$yes"),
false => b::primitive("$no"),
},
Primitive::Date(date) => primitive_doc(date, "date"),
Primitive::Duration(duration) => primitive_doc(duration, "seconds"),
Primitive::Path(path) => primitive_doc(path, "path"),
Primitive::Binary(_) => b::opaque("binary"),
Primitive::BeginningOfStream => b::keyword("beginning-of-stream"),
Primitive::EndOfStream => b::keyword("end-of-stream"),
}
}
}
impl PrettyDebug for Value {
fn pretty_debug(&self) -> DebugDocBuilder {
match self {
Value::Primitive(p) => p.pretty_debug(),
Value::Row(row) => row.pretty_builder().nest(1).group().into(),
Value::Table(table) => BoxAllocator
.text("[")
.append(
BoxAllocator
.intersperse(table.iter().map(|v| v.item.to_doc()), BoxAllocator.space())
.nest(1)
.group(),
)
.append(BoxAllocator.text("]"))
.into(),
Value::Error(_) => b::error("error"),
Value::Block(_) => b::opaque("block"),
}
}
}
fn prim(name: impl std::fmt::Debug) -> DebugDocBuilder {
b::primitive(format!("{:?}", name))
}
fn ty(name: impl std::fmt::Debug) -> DebugDocBuilder {
b::kind(format!("{:?}", name))
}
fn primitive_doc(name: impl std::fmt::Debug, ty: impl Into<String>) -> DebugDocBuilder {
b::primitive(format!("{:?}", name)) + b::delimit("(", b::kind(ty.into()), ")")
}

View File

@ -0,0 +1,407 @@
use crate::errors::ExpectedRange;
use crate::parser::hir::path::{PathMember, RawPathMember};
use crate::prelude::*;
use crate::ColumnPath;
use crate::SpannedTypeName;
impl Tagged<Value> {
pub(crate) fn get_data_by_member(
&self,
name: &PathMember,
) -> Result<Tagged<Value>, ShellError> {
match &self.item {
// If the value is a row, the member is a column name
Value::Row(o) => match &name.item {
// If the member is a string, get the data
RawPathMember::String(string) => o
.get_data_by_key(string[..].spanned(name.span))
.ok_or_else(|| {
ShellError::missing_property(
"row".spanned(self.tag.span),
string.spanned(name.span),
)
}),
// If the member is a number, it's an error
RawPathMember::Int(_) => Err(ShellError::invalid_integer_index(
"row".spanned(self.tag.span),
name.span,
)),
},
// If the value is a table
Value::Table(l) => match &name.item {
// If the member is a string, map over the member
RawPathMember::String(string) => {
let mut out = vec![];
for item in l {
match item {
Tagged {
item: Value::Row(o),
..
} => match o.get_data_by_key(string[..].spanned(name.span)) {
Some(v) => out.push(v),
None => {}
},
_ => {}
}
}
if out.len() == 0 {
Err(ShellError::missing_property(
"table".spanned(self.tag.span),
string.spanned(name.span),
))
} else {
Ok(Value::Table(out).tagged(Tag::new(self.anchor(), name.span)))
}
}
RawPathMember::Int(int) => {
let index = int.to_usize().ok_or_else(|| {
ShellError::range_error(
ExpectedRange::Usize,
&"massive integer".tagged(name.span),
"indexing",
)
})?;
match self.get_data_by_index(index.spanned(self.tag.span)) {
Some(v) => Ok(v.clone()),
None => Err(ShellError::range_error(
0..(l.len()),
&int.tagged(name.span),
"indexing",
)),
}
}
},
other => Err(ShellError::type_error(
"row or table",
other.spanned(self.tag.span).spanned_type_name(),
)),
}
}
pub fn get_data_by_column_path(
&self,
path: &ColumnPath,
callback: Box<dyn FnOnce((&Value, &PathMember, ShellError)) -> ShellError>,
) -> Result<Tagged<Value>, ShellError> {
let mut current = self.clone();
for p in path.iter() {
let value = current.get_data_by_member(p);
match value {
Ok(v) => current = v.clone(),
Err(e) => return Err(callback((&current.clone(), &p.clone(), e))),
}
}
Ok(current)
}
pub fn insert_data_at_path(&self, path: &str, new_value: Value) -> Option<Tagged<Value>> {
let mut new_obj = self.clone();
let split_path: Vec<_> = path.split(".").collect();
if let Value::Row(ref mut o) = new_obj.item {
let mut current = o;
if split_path.len() == 1 {
// Special case for inserting at the top level
current
.entries
.insert(path.to_string(), new_value.tagged(&self.tag));
return Some(new_obj);
}
for idx in 0..split_path.len() {
match current.entries.get_mut(split_path[idx]) {
Some(next) => {
if idx == (split_path.len() - 2) {
match &mut next.item {
Value::Row(o) => {
o.entries.insert(
split_path[idx + 1].to_string(),
new_value.tagged(&self.tag),
);
}
_ => {}
}
return Some(new_obj.clone());
} else {
match next.item {
Value::Row(ref mut o) => {
current = o;
}
_ => return None,
}
}
}
_ => return None,
}
}
}
None
}
pub fn insert_data_at_member(
&mut self,
member: &PathMember,
new_value: Tagged<Value>,
) -> Result<(), ShellError> {
match &mut self.item {
Value::Row(dict) => match &member.item {
RawPathMember::String(key) => Ok({
dict.insert_data_at_key(key, new_value);
}),
RawPathMember::Int(_) => Err(ShellError::type_error(
"column name",
"integer".spanned(member.span),
)),
},
Value::Table(array) => match &member.item {
RawPathMember::String(_) => Err(ShellError::type_error(
"list index",
"string".spanned(member.span),
)),
RawPathMember::Int(int) => Ok({
let int = int.to_usize().ok_or_else(|| {
ShellError::range_error(
ExpectedRange::Usize,
&"bigger number".tagged(member.span),
"inserting into a list",
)
})?;
insert_data_at_index(array, int.tagged(member.span), new_value.clone())?;
}),
},
other => match &member.item {
RawPathMember::String(_) => Err(ShellError::type_error(
"row",
other.type_name().spanned(self.span()),
)),
RawPathMember::Int(_) => Err(ShellError::type_error(
"table",
other.type_name().spanned(self.span()),
)),
},
}
}
pub fn insert_data_at_column_path(
&self,
split_path: &ColumnPath,
new_value: Tagged<Value>,
) -> Result<Tagged<Value>, ShellError> {
let (last, front) = split_path.split_last();
let mut original = self.clone();
let mut current: &mut Tagged<Value> = &mut original;
for member in front {
let type_name = current.spanned_type_name();
current = current
.item
.get_mut_data_by_member(&member)
.ok_or_else(|| {
ShellError::missing_property(
member.plain_string(std::usize::MAX).spanned(member.span),
type_name,
)
})?
}
current.insert_data_at_member(&last, new_value)?;
Ok(original)
}
pub fn replace_data_at_column_path(
&self,
split_path: &ColumnPath,
replaced_value: Value,
) -> Option<Tagged<Value>> {
let mut new_obj: Tagged<Value> = self.clone();
let mut current = &mut new_obj;
let split_path = split_path.members();
for idx in 0..split_path.len() {
match current.item.get_mut_data_by_member(&split_path[idx]) {
Some(next) => {
if idx == (split_path.len() - 1) {
*next = replaced_value.tagged(&self.tag);
return Some(new_obj);
} else {
current = next;
}
}
None => {
return None;
}
}
}
None
}
pub fn as_column_path(&self) -> Result<Tagged<ColumnPath>, ShellError> {
match &self.item {
Value::Table(table) => {
let mut out: Vec<PathMember> = vec![];
for item in table {
out.push(item.as_path_member()?);
}
Ok(ColumnPath::new(out).tagged(&self.tag))
}
Value::Primitive(Primitive::ColumnPath(path)) => {
Ok(path.clone().tagged(self.tag.clone()))
}
other => Err(ShellError::type_error(
"column path",
other.type_name().spanned(self.span()),
)),
}
}
pub fn as_path_member(&self) -> Result<PathMember, ShellError> {
match &self.item {
Value::Primitive(primitive) => match primitive {
Primitive::Int(int) => Ok(PathMember::int(int.clone(), self.tag.span)),
Primitive::String(string) => Ok(PathMember::string(string, self.tag.span)),
other => Err(ShellError::type_error(
"path member",
other.type_name().spanned(self.span()),
)),
},
other => Err(ShellError::type_error(
"path member",
other.type_name().spanned(self.span()),
)),
}
}
pub fn as_string(&self) -> Result<String, ShellError> {
match &self.item {
Value::Primitive(Primitive::String(s)) => Ok(s.clone()),
Value::Primitive(Primitive::Boolean(x)) => Ok(format!("{}", x)),
Value::Primitive(Primitive::Decimal(x)) => Ok(format!("{}", x)),
Value::Primitive(Primitive::Int(x)) => Ok(format!("{}", x)),
Value::Primitive(Primitive::Bytes(x)) => Ok(format!("{}", x)),
Value::Primitive(Primitive::Path(x)) => Ok(format!("{}", x.display())),
// TODO: this should definitely be more general with better errors
other => Err(ShellError::labeled_error(
"Expected string",
other.type_name(),
&self.tag,
)),
}
}
}
fn insert_data_at_index(
list: &mut Vec<Tagged<Value>>,
index: Tagged<usize>,
new_value: Tagged<Value>,
) -> Result<(), ShellError> {
if list.len() >= index.item {
Err(ShellError::range_error(
0..(list.len()),
&format_args!("{}", index.item).tagged(index.tag.clone()),
"insert at index",
))
} else {
list[index.item] = new_value;
Ok(())
}
}
impl Value {
pub(crate) fn get_data_by_index(&self, idx: Spanned<usize>) -> Option<Tagged<Value>> {
match self {
Value::Table(value_set) => {
let value = value_set.get(idx.item)?;
Some(
value
.item
.clone()
.tagged(Tag::new(value.anchor(), idx.span)),
)
}
_ => None,
}
}
pub(crate) fn get_data_by_key(&self, name: Spanned<&str>) -> Option<Tagged<Value>> {
match self {
Value::Row(o) => o.get_data_by_key(name),
Value::Table(l) => {
let mut out = vec![];
for item in l {
match item {
Tagged {
item: Value::Row(o),
..
} => match o.get_data_by_key(name) {
Some(v) => out.push(v),
None => out.push(Value::nothing().tagged_unknown()),
},
_ => out.push(Value::nothing().tagged_unknown()),
}
}
if out.len() > 0 {
Some(Value::Table(out).tagged(name.span))
} else {
None
}
}
_ => None,
}
}
pub(crate) fn get_mut_data_by_member(
&mut self,
name: &PathMember,
) -> Option<&mut Tagged<Value>> {
match self {
Value::Row(o) => match &name.item {
RawPathMember::String(string) => o.get_mut_data_by_key(&string),
RawPathMember::Int(_) => None,
},
Value::Table(l) => match &name.item {
RawPathMember::String(string) => {
for item in l {
match item {
Tagged {
item: Value::Row(o),
..
} => match o.get_mut_data_by_key(&string) {
Some(v) => return Some(v),
None => {}
},
_ => {}
}
}
None
}
RawPathMember::Int(int) => {
let index = int.to_usize()?;
l.get_mut(index)
}
},
_ => None,
}
}
}

606
src/data/base/shape.rs Normal file
View File

@ -0,0 +1,606 @@
use crate::data::base::{Block, ColumnPath};
use crate::data::dict::Dictionary;
use crate::prelude::*;
use crate::traits::{DebugDocBuilder as b, PrettyDebug};
use chrono::{DateTime, Utc};
use chrono_humanize::Humanize;
use derive_new::new;
use indexmap::IndexMap;
use std::collections::BTreeMap;
use std::fmt::Debug;
use std::hash::Hash;
use std::io::Write;
use std::path::PathBuf;
/**
This file describes the structural types of the nushell system.
Its primary purpose today is to identify "equivalent" values for the purpose
of merging rows into a single table or identify rows in a table that have the
same shape for reflection.
It also serves as the primary vehicle for pretty-printing.
*/
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum TypeShape {
Nothing,
Int,
Decimal,
Bytesize,
String,
ColumnPath,
Pattern,
Boolean,
Date,
Duration,
Path,
Binary,
Row(BTreeMap<Column, TypeShape>),
Table(Vec<TypeShape>),
// TODO: Block arguments
Block,
// TODO: Error type
Error,
// Stream markers (used as bookend markers rather than actual values)
BeginningOfStream,
EndOfStream,
}
impl TypeShape {
pub fn from_primitive(primitive: &Primitive) -> TypeShape {
match primitive {
Primitive::Nothing => TypeShape::Nothing,
Primitive::Int(_) => TypeShape::Int,
Primitive::Decimal(_) => TypeShape::Decimal,
Primitive::Bytes(_) => TypeShape::Bytesize,
Primitive::String(_) => TypeShape::String,
Primitive::ColumnPath(_) => TypeShape::ColumnPath,
Primitive::Pattern(_) => TypeShape::Pattern,
Primitive::Boolean(_) => TypeShape::Boolean,
Primitive::Date(_) => TypeShape::Date,
Primitive::Duration(_) => TypeShape::Duration,
Primitive::Path(_) => TypeShape::Path,
Primitive::Binary(_) => TypeShape::Binary,
Primitive::BeginningOfStream => TypeShape::BeginningOfStream,
Primitive::EndOfStream => TypeShape::EndOfStream,
}
}
pub fn from_dictionary(dictionary: &Dictionary) -> TypeShape {
let mut map = BTreeMap::new();
for (key, value) in dictionary.entries.iter() {
let column = Column::String(key.clone());
map.insert(column, TypeShape::from_value(&value.item));
}
TypeShape::Row(map)
}
pub fn from_table<'a>(table: impl IntoIterator<Item = &'a Value>) -> TypeShape {
let mut vec = vec![];
for item in table.into_iter() {
vec.push(TypeShape::from_value(item))
}
TypeShape::Table(vec)
}
pub fn from_value(value: &Value) -> TypeShape {
match value {
Value::Primitive(p) => TypeShape::from_primitive(p),
Value::Row(row) => TypeShape::from_dictionary(row),
Value::Table(table) => TypeShape::from_table(table.iter().map(|i| &i.item)),
Value::Error(_) => TypeShape::Error,
Value::Block(_) => TypeShape::Block,
}
}
}
impl PrettyDebug for TypeShape {
fn pretty_debug(&self) -> DebugDocBuilder {
match self {
TypeShape::Nothing => ty("nothing"),
TypeShape::Int => ty("integer"),
TypeShape::Decimal => ty("decimal"),
TypeShape::Bytesize => ty("bytesize"),
TypeShape::String => ty("string"),
TypeShape::ColumnPath => ty("column-path"),
TypeShape::Pattern => ty("pattern"),
TypeShape::Boolean => ty("boolean"),
TypeShape::Date => ty("date"),
TypeShape::Duration => ty("duration"),
TypeShape::Path => ty("path"),
TypeShape::Binary => ty("binary"),
TypeShape::Error => b::error("error"),
TypeShape::BeginningOfStream => b::keyword("beginning-of-stream"),
TypeShape::EndOfStream => b::keyword("end-of-stream"),
TypeShape::Row(row) => (b::kind("row")
+ b::space()
+ b::intersperse(
row.iter().map(|(key, ty)| {
(b::key(match key {
Column::String(string) => string.clone(),
Column::Value => "<value>".to_string(),
}) + b::delimit("(", ty.pretty_debug(), ")").as_kind())
.nest()
}),
b::space(),
)
.nest())
.nest(),
TypeShape::Table(table) => {
let mut group: Group<DebugDoc, Vec<(usize, usize)>> = Group::new();
for (i, item) in table.iter().enumerate() {
group.add(item.to_doc(), i);
}
(b::kind("table") + b::space() + b::keyword("of")).group()
+ b::space()
+ (if group.len() == 1 {
let (doc, _) = group.into_iter().nth(0).unwrap();
DebugDocBuilder::from_doc(doc)
} else {
b::intersperse(
group.into_iter().map(|(doc, rows)| {
(b::intersperse(
rows.iter().map(|(from, to)| {
if from == to {
b::description(from)
} else {
(b::description(from)
+ b::space()
+ b::keyword("to")
+ b::space()
+ b::description(to))
.group()
}
}),
b::description(", "),
) + b::description(":")
+ b::space()
+ DebugDocBuilder::from_doc(doc))
.nest()
}),
b::space(),
)
})
}
TypeShape::Block => ty("block"),
}
}
}
#[derive(Debug, new)]
struct DebugEntry<'a> {
key: &'a Column,
value: &'a TypeShape,
}
impl<'a> PrettyDebug for DebugEntry<'a> {
fn pretty_debug(&self) -> DebugDocBuilder {
(b::key(match self.key {
Column::String(string) => string.clone(),
Column::Value => format!("<value>"),
}) + b::delimit("(", self.value.pretty_debug(), ")").as_kind())
}
}
fn ty(name: impl std::fmt::Display) -> DebugDocBuilder {
b::kind(format!("{}", name))
}
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum InlineShape {
Nothing,
Int(BigInt),
Decimal(BigDecimal),
Bytesize(u64),
String(String),
ColumnPath(ColumnPath),
Pattern(String),
Boolean(bool),
Date(DateTime<Utc>),
Duration(u64),
Path(PathBuf),
Binary,
Row(BTreeMap<Column, InlineShape>),
Table(Vec<InlineShape>),
// TODO: Block arguments
Block,
// TODO: Error type
Error,
// Stream markers (used as bookend markers rather than actual values)
BeginningOfStream,
EndOfStream,
}
pub struct FormatInlineShape {
shape: InlineShape,
column: Option<Column>,
}
impl InlineShape {
pub fn from_primitive(primitive: &Primitive) -> InlineShape {
match primitive {
Primitive::Nothing => InlineShape::Nothing,
Primitive::Int(int) => InlineShape::Int(int.clone()),
Primitive::Decimal(decimal) => InlineShape::Decimal(decimal.clone()),
Primitive::Bytes(bytesize) => InlineShape::Bytesize(*bytesize),
Primitive::String(string) => InlineShape::String(string.clone()),
Primitive::ColumnPath(path) => InlineShape::ColumnPath(path.clone()),
Primitive::Pattern(pattern) => InlineShape::Pattern(pattern.clone()),
Primitive::Boolean(boolean) => InlineShape::Boolean(*boolean),
Primitive::Date(date) => InlineShape::Date(date.clone()),
Primitive::Duration(duration) => InlineShape::Duration(*duration),
Primitive::Path(path) => InlineShape::Path(path.clone()),
Primitive::Binary(_) => InlineShape::Binary,
Primitive::BeginningOfStream => InlineShape::BeginningOfStream,
Primitive::EndOfStream => InlineShape::EndOfStream,
}
}
pub fn from_dictionary(dictionary: &Dictionary) -> InlineShape {
let mut map = BTreeMap::new();
for (key, value) in dictionary.entries.iter() {
let column = Column::String(key.clone());
map.insert(column, InlineShape::from_value(&value.item));
}
InlineShape::Row(map)
}
pub fn from_table<'a>(table: impl IntoIterator<Item = &'a Value>) -> InlineShape {
let mut vec = vec![];
for item in table.into_iter() {
vec.push(InlineShape::from_value(item))
}
InlineShape::Table(vec)
}
pub fn from_value(value: &Value) -> InlineShape {
match value {
Value::Primitive(p) => InlineShape::from_primitive(p),
Value::Row(row) => InlineShape::from_dictionary(row),
Value::Table(table) => InlineShape::from_table(table.iter().map(|i| &i.item)),
Value::Error(_) => InlineShape::Error,
Value::Block(_) => InlineShape::Block,
}
}
pub fn format_for_column(self, column: impl Into<Column>) -> FormatInlineShape {
FormatInlineShape {
shape: self,
column: Some(column.into()),
}
}
pub fn format(self) -> FormatInlineShape {
FormatInlineShape {
shape: self,
column: None,
}
}
}
impl PrettyDebug for FormatInlineShape {
fn pretty_debug(&self) -> DebugDocBuilder {
let column = &self.column;
match &self.shape {
InlineShape::Nothing => b::blank(),
InlineShape::Int(int) => b::primitive(format!("{}", int)),
InlineShape::Decimal(decimal) => b::primitive(format!("{}", decimal)),
InlineShape::Bytesize(bytesize) => {
let byte = byte_unit::Byte::from_bytes(*bytesize as u128);
if byte.get_bytes() == 0u128 {
return b::description("".to_string());
}
let byte = byte.get_appropriate_unit(false);
match byte.get_unit() {
byte_unit::ByteUnit::B => {
(b::primitive(format!("{}", byte.get_value())) + b::space() + b::kind("B"))
.group()
}
_ => b::primitive(format!("{}", byte.format(1))),
}
}
InlineShape::String(string) => b::primitive(format!("{}", string)),
InlineShape::ColumnPath(path) => b::intersperse(
path.iter().map(|member| member.pretty_debug()),
b::keyword("."),
),
InlineShape::Pattern(pattern) => b::primitive(pattern),
InlineShape::Boolean(boolean) => b::primitive(match (boolean, column) {
(true, None) => format!("Yes"),
(false, None) => format!("No"),
(true, Some(Column::String(s))) if !s.is_empty() => format!("{}", s),
(false, Some(Column::String(s))) if !s.is_empty() => format!(""),
(true, Some(_)) => format!("Yes"),
(false, Some(_)) => format!("No"),
}),
InlineShape::Date(date) => b::primitive(date.humanize()),
InlineShape::Duration(duration) => {
(b::kind("duration") + b::space() + b::primitive(duration)).group()
}
InlineShape::Path(path) => b::primitive(path.display()),
InlineShape::Binary => b::opaque("<binary>"),
InlineShape::Row(row) => b::delimit(
"[",
b::kind("row")
+ b::space()
+ b::intersperse(
row.keys().map(|key| match key {
Column::String(string) => b::description(string),
Column::Value => b::blank(),
}),
b::space(),
),
"]",
)
.group(),
InlineShape::Table(rows) => b::delimit(
"[",
b::kind("table")
+ b::space()
+ b::primitive(rows.len())
+ b::space()
+ b::description("rows"),
"]",
)
.group(),
InlineShape::Block => b::opaque("block"),
InlineShape::Error => b::error("error"),
InlineShape::BeginningOfStream => b::blank(),
InlineShape::EndOfStream => b::blank(),
}
}
}
pub trait GroupedValue: Debug + Clone {
type Item;
fn new() -> Self;
fn merge(&mut self, value: Self::Item);
}
impl GroupedValue for Vec<(usize, usize)> {
type Item = usize;
fn new() -> Vec<(usize, usize)> {
vec![]
}
fn merge(&mut self, new_value: usize) {
match self.last_mut() {
Some(value) if value.1 == new_value - 1 => {
value.1 += 1;
}
_ => self.push((new_value, new_value)),
}
}
}
#[derive(Debug)]
pub struct Group<K: Debug + Eq + Hash, V: GroupedValue> {
values: indexmap::IndexMap<K, V>,
}
impl<K, G> Group<K, G>
where
K: Debug + Eq + Hash,
G: GroupedValue,
{
pub fn new() -> Group<K, G> {
Group {
values: indexmap::IndexMap::default(),
}
}
pub fn len(&self) -> usize {
self.values.len()
}
pub fn into_iter(self) -> impl Iterator<Item = (K, G)> {
self.values.into_iter()
}
pub fn add(&mut self, key: impl Into<K>, value: impl Into<G::Item>) {
let key = key.into();
let value = value.into();
let group = self.values.get_mut(&key);
match group {
None => {
self.values.insert(key, {
let mut group = G::new();
group.merge(value.into());
group
});
}
Some(group) => {
group.merge(value.into());
}
}
}
}
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum Column {
String(String),
Value,
}
impl Into<Column> for String {
fn into(self) -> Column {
Column::String(self)
}
}
impl Into<Column> for &String {
fn into(self) -> Column {
Column::String(self.clone())
}
}
impl Into<Column> for &str {
fn into(self) -> Column {
Column::String(self.to_string())
}
}
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum Shape {
Primitive(&'static str),
Row(Vec<Column>),
Table { from: usize, to: usize },
Error(ShellError),
Block(Block),
}
impl Value {
pub fn shape(&self) -> Shape {
Shape::for_value(self)
}
}
impl Shape {
pub fn for_value(value: &Value) -> Shape {
match value {
Value::Primitive(p) => Shape::Primitive(p.type_name()),
Value::Row(row) => Shape::for_dict(row),
Value::Table(table) => Shape::Table {
from: 0,
to: table.len(),
},
Value::Error(error) => Shape::Error(error.clone()),
Value::Block(block) => Shape::Block(block.clone()),
}
}
fn for_dict(dict: &Dictionary) -> Shape {
Shape::Row(dict.keys().map(|key| Column::String(key.clone())).collect())
}
pub fn kind(&self) -> String {
match self {
Shape::Primitive(primitive) => primitive,
Shape::Row(row) => {
return row
.iter()
.map(|c| match c {
Column::String(s) => s.clone(),
Column::Value => format!("<value>"),
})
.join(", ")
}
Shape::Table { .. } => "table",
Shape::Error(_) => "error",
Shape::Block(_) => "block",
}
.to_string()
}
pub fn describe_str(&self) -> String {
let mut v = vec![];
self.describe(&mut v)
.expect("it isn't possible to fail to write into a memory buffer");
String::from_utf8_lossy(&v[..]).to_string()
}
pub fn describe(&self, w: &mut impl Write) -> Result<(), std::io::Error> {
match self {
Shape::Primitive(desc) => write!(w, "[{}]", desc),
Shape::Row(d) => write!(
w,
"[row: {}]",
d.iter()
.map(|c| match c {
Column::String(s) => s.clone(),
Column::Value => format!("<value>"),
})
.join(", ")
),
Shape::Table { to, .. } => {
if *to == 1 {
write!(w, "[table: {} row]", to)
} else {
write!(w, "[table: {} rows]", to)
}
}
Shape::Error(_) => write!(w, "[error]"),
Shape::Block(_) => write!(w, "[block]"),
}
}
fn to_value(&self) -> Value {
let mut out = vec![];
self.describe(&mut out)
.expect("Writing into a Vec can't fail");
let string = String::from_utf8_lossy(&out);
Value::string(string)
}
}
pub struct Shapes {
shapes: IndexMap<Shape, Vec<usize>>,
}
impl Shapes {
pub fn new() -> Shapes {
Shapes {
shapes: IndexMap::default(),
}
}
pub fn add(&mut self, value: &Value, row: usize) {
let shape = Shape::for_value(value);
self.shapes
.entry(shape)
.and_modify(|indexes| indexes.push(row))
.or_insert_with(|| vec![row]);
}
pub fn to_values(&self) -> Vec<Tagged<Value>> {
if self.shapes.len() == 1 {
let shape = self.shapes.keys().nth(0).unwrap();
vec![dict! {
"type" => shape.to_value(),
"rows" => Value::string("all")
}]
} else {
self.shapes
.iter()
.map(|(shape, rows)| {
let rows = rows.iter().map(|i| i.to_string()).join(", ");
dict! {
"type" => shape.to_value(),
"rows" => Value::string(format!("[ {} ]", rows))
}
})
.collect()
}
}
}

View File

@ -98,7 +98,7 @@ pub fn read(
Value::Row(Dictionary { entries }) => Ok(entries),
other => Err(ShellError::type_error(
"Dictionary",
other.type_name().tagged(&tag),
other.type_name().spanned(tag.span),
)),
}
}

View File

@ -1,16 +1,69 @@
use crate::data::{Primitive, Value};
use crate::prelude::*;
use crate::traits::{DebugDocBuilder as b, PrettyDebug};
use derive_new::new;
use getset::Getters;
use indexmap::IndexMap;
use pretty::{BoxAllocator, DocAllocator};
use serde::{Deserialize, Serialize};
use std::cmp::{Ordering, PartialOrd};
use std::fmt;
#[derive(Debug, Default, Eq, PartialEq, Serialize, Deserialize, Clone, new)]
#[derive(Debug, Default, Eq, PartialEq, Serialize, Deserialize, Clone, Getters, new)]
pub struct Dictionary {
#[get = "pub"]
pub entries: IndexMap<String, Tagged<Value>>,
}
#[derive(Debug, new)]
struct DebugEntry<'a> {
key: &'a str,
value: &'a Tagged<Value>,
}
impl<'a> PrettyDebug for DebugEntry<'a> {
fn pretty_debug(&self) -> DebugDocBuilder {
(b::key(self.key.to_string()) + b::equals() + self.value.item.pretty_debug().as_value())
.group()
// BoxAllocator
// .text(self.key.to_string())
// .annotate(ShellAnnotation::style("key"))
// .append(
// BoxAllocator
// .text("=")
// .annotate(ShellAnnotation::style("equals")),
// )
// .append({
// self.value
// .item
// .pretty_debug()
// .inner
// .annotate(ShellAnnotation::style("value"))
// })
// .group()
// .into()
}
}
impl PrettyDebug for Dictionary {
fn pretty_debug(&self) -> DebugDocBuilder {
BoxAllocator
.text("(")
.append(
BoxAllocator
.intersperse(
self.entries()
.iter()
.map(|(key, value)| DebugEntry::new(key, value).to_doc()),
BoxAllocator.space(),
)
.nest(1)
.group(),
)
.append(BoxAllocator.text(")"))
.into()
}
}
impl PartialOrd for Dictionary {
fn partial_cmp(&self, other: &Dictionary) -> Option<Ordering> {
let this: Vec<&String> = self.entries.keys().collect();
@ -78,15 +131,23 @@ impl Dictionary {
}
}
pub(crate) fn get_data_by_key(&self, name: &str) -> Option<&Tagged<Value>> {
match self
pub fn keys(&self) -> impl Iterator<Item = &String> {
self.entries.keys()
}
pub(crate) fn get_data_by_key(&self, name: Spanned<&str>) -> Option<Tagged<Value>> {
let result = self
.entries
.iter()
.find(|(desc_name, _)| *desc_name == name)
{
Some((_, v)) => Some(v),
None => None,
}
.find(|(desc_name, _)| *desc_name == name.item)?
.1;
Some(
result
.item
.clone()
.tagged(Tag::new(result.anchor(), name.span)),
)
}
pub(crate) fn get_mut_data_by_key(&mut self, name: &str) -> Option<&mut Tagged<Value>> {
@ -100,14 +161,8 @@ impl Dictionary {
}
}
pub(crate) fn debug(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut debug = f.debug_struct("Dictionary");
for (desc, value) in self.entries.iter() {
debug.field(desc, &value.debug());
}
debug.finish()
pub(crate) fn insert_data_at_key(&mut self, name: &str, value: Tagged<Value>) {
self.entries.insert(name.to_string(), value);
}
}
@ -158,6 +213,12 @@ impl TaggedDictBuilder {
}
}
pub fn build(tag: impl Into<Tag>, block: impl FnOnce(&mut TaggedDictBuilder)) -> Tagged<Value> {
let mut builder = TaggedDictBuilder::new(tag);
block(&mut builder);
builder.into_tagged_value()
}
pub fn with_capacity(tag: impl Into<Tag>, n: usize) -> TaggedDictBuilder {
TaggedDictBuilder {
tag: tag.into(),

View File

@ -23,6 +23,21 @@ impl<T> Spanned<T> {
}
}
impl Spanned<String> {
pub fn items<'a, U>(
items: impl Iterator<Item = &'a Spanned<String>>,
) -> impl Iterator<Item = &'a str> {
items.into_iter().map(|item| &item.item[..])
}
}
impl Spanned<String> {
pub fn borrow_spanned(&self) -> Spanned<&str> {
let span = self.span;
self.item[..].spanned(span)
}
}
pub trait SpannedItem: Sized {
fn spanned(self, span: impl Into<Span>) -> Spanned<Self> {
Spanned {
@ -53,6 +68,17 @@ pub struct Tagged<T> {
pub item: T,
}
impl Tagged<String> {
pub fn borrow_spanned(&self) -> Spanned<&str> {
let span = self.tag.span;
self.item[..].spanned(span)
}
pub fn borrow_tagged(&self) -> Tagged<&str> {
self.item[..].tagged(self.tag.clone())
}
}
impl<T> HasTag for Tagged<T> {
fn tag(&self) -> Tag {
self.tag.clone()
@ -116,6 +142,13 @@ impl<T> Tagged<T> {
}
}
pub fn transpose(&self) -> Tagged<&T> {
Tagged {
item: &self.item,
tag: self.tag.clone(),
}
}
pub fn tag(&self) -> Tag {
self.tag.clone()
}
@ -362,6 +395,23 @@ pub fn tag_for_tagged_list(mut iter: impl Iterator<Item = Tag>) -> Tag {
}
}
#[allow(unused)]
pub fn span_for_spanned_list(mut iter: impl Iterator<Item = Span>) -> Span {
let first = iter.next();
let first = match first {
None => return Span::unknown(),
Some(first) => first,
};
let last = iter.last();
match last {
None => first,
Some(last) => first.until(last),
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Serialize, Deserialize, Hash)]
pub struct Span {
start: usize,

View File

@ -27,7 +27,10 @@ impl ExtractType for bool {
item: Value::Primitive(Primitive::Nothing),
..
} => Ok(false),
other => Err(ShellError::type_error("Boolean", other.tagged_type_name())),
other => Err(ShellError::type_error(
"Boolean",
other.type_name().spanned(other.span()),
)),
}
}
}
@ -41,7 +44,10 @@ impl ExtractType for std::path::PathBuf {
item: Value::Primitive(Primitive::Path(p)),
..
} => Ok(p.clone()),
other => Err(ShellError::type_error("Path", other.tagged_type_name())),
other => Err(ShellError::type_error(
"Path",
other.type_name().spanned(other.span()),
)),
}
}
}
@ -55,7 +61,10 @@ impl ExtractType for i64 {
item: Value::Primitive(Primitive::Int(int)),
..
} => Ok(int.tagged(&value.tag).coerce_into("converting to i64")?),
other => Err(ShellError::type_error("Integer", other.tagged_type_name())),
other => Err(ShellError::type_error(
"Integer",
other.type_name().spanned(other.span()),
)),
}
}
}
@ -69,7 +78,10 @@ impl ExtractType for u64 {
item: Value::Primitive(Primitive::Int(int)),
..
} => Ok(int.tagged(&value.tag).coerce_into("converting to u64")?),
other => Err(ShellError::type_error("Integer", other.tagged_type_name())),
other => Err(ShellError::type_error(
"Integer",
other.type_name().spanned(other.span()),
)),
}
}
}
@ -83,7 +95,10 @@ impl ExtractType for String {
item: Value::Primitive(Primitive::String(string)),
..
} => Ok(string.clone()),
other => Err(ShellError::type_error("String", other.tagged_type_name())),
other => Err(ShellError::type_error(
"String",
other.type_name().spanned(other.span()),
)),
}
}
}

19
src/env/host.rs vendored
View File

@ -11,6 +11,8 @@ pub trait Host: Debug + Send {
fn stdout(&mut self, out: &str);
fn stderr(&mut self, out: &str);
fn width(&self) -> usize;
}
impl Host for Box<dyn Host> {
@ -37,6 +39,10 @@ impl Host for Box<dyn Host> {
fn err_termcolor(&self) -> termcolor::StandardStream {
(**self).err_termcolor()
}
fn width(&self) -> usize {
(**self).width()
}
}
#[derive(Debug)]
@ -53,24 +59,29 @@ impl Host for BasicHost {
fn stdout(&mut self, out: &str) {
match out {
"\n" => println!(""),
other => println!("{}", other),
"\n" => outln!(""),
other => outln!("{}", other),
}
}
fn stderr(&mut self, out: &str) {
match out {
"\n" => eprintln!(""),
other => eprintln!("{}", other),
"\n" => errln!(""),
other => errln!("{}", other),
}
}
fn out_termcolor(&self) -> termcolor::StandardStream {
termcolor::StandardStream::stdout(termcolor::ColorChoice::Auto)
}
fn err_termcolor(&self) -> termcolor::StandardStream {
termcolor::StandardStream::stderr(termcolor::ColorChoice::Auto)
}
fn width(&self) -> usize {
std::cmp::max(textwrap::termwidth(), 20)
}
}
pub(crate) fn handle_unexpected<T>(

View File

@ -6,13 +6,20 @@ use derive_new::new;
use language_reporting::{Diagnostic, Label, Severity};
use serde::{Deserialize, Serialize};
use std::fmt;
use std::ops::Range;
#[derive(Debug, Eq, PartialEq, Clone, Ord, PartialOrd, Serialize, Deserialize)]
#[derive(Debug, Eq, PartialEq, Clone, Ord, PartialOrd, Hash, Serialize, Deserialize)]
pub enum Description {
Source(Tagged<String>),
Source(Spanned<String>),
Synthetic(String),
}
impl<T: Into<String>> Into<Description> for Spanned<T> {
fn into(self) -> Description {
Description::Source(self.map(|s| s.into()))
}
}
impl Description {
fn into_label(self) -> Result<Label<Span>, String> {
match self {
@ -20,72 +27,53 @@ impl Description {
Description::Synthetic(s) => Err(s),
}
}
#[allow(unused)]
fn tag(&self) -> Tag {
match self {
Description::Source(tagged) => tagged.tag.clone(),
Description::Synthetic(_) => Tag::unknown(),
}
}
}
#[derive(Debug, Clone)]
pub enum ParseErrorReason {
Eof {
expected: &'static str,
span: Span,
},
Mismatch {
expected: &'static str,
actual: Tagged<String>,
actual: Spanned<String>,
},
ArgumentError {
command: String,
command: Spanned<String>,
error: ArgumentError,
tag: Tag,
},
}
#[derive(Debug, Clone)]
pub struct ParseError {
reason: ParseErrorReason,
tag: Tag,
}
impl ParseError {
pub fn unexpected_eof(expected: &'static str, span: Span) -> ParseError {
ParseError {
reason: ParseErrorReason::Eof { expected },
tag: span.into(),
reason: ParseErrorReason::Eof { expected, span },
}
}
pub fn mismatch(expected: &'static str, actual: Tagged<impl Into<String>>) -> ParseError {
let Tagged { tag, item } = actual;
pub fn mismatch(expected: &'static str, actual: Spanned<impl Into<String>>) -> ParseError {
let Spanned { span, item } = actual;
ParseError {
reason: ParseErrorReason::Mismatch {
expected,
actual: item.into().tagged(tag.clone()),
actual: item.into().spanned(span),
},
tag,
}
}
pub fn argument_error(
command: impl Into<String>,
kind: ArgumentError,
tag: impl Into<Tag>,
) -> ParseError {
let tag = tag.into();
pub fn argument_error(command: Spanned<impl Into<String>>, kind: ArgumentError) -> ParseError {
ParseError {
reason: ParseErrorReason::ArgumentError {
command: command.into(),
command: command.item.into().spanned(command.span),
error: kind,
tag: tag.clone(),
},
tag: tag.clone(),
}
}
}
@ -93,20 +81,18 @@ impl ParseError {
impl From<ParseError> for ShellError {
fn from(error: ParseError) -> ShellError {
match error.reason {
ParseErrorReason::Eof { expected } => ShellError::unexpected_eof(expected, error.tag),
ParseErrorReason::Eof { expected, span } => ShellError::unexpected_eof(expected, span),
ParseErrorReason::Mismatch { actual, expected } => {
ShellError::type_error(expected, actual.clone())
}
ParseErrorReason::ArgumentError {
command,
error,
tag,
} => ShellError::argument_error(command, error, tag),
ParseErrorReason::ArgumentError { command, error } => {
ShellError::argument_error(command, error)
}
}
}
}
#[derive(Debug, Eq, PartialEq, Clone, Ord, PartialOrd, Serialize, Deserialize)]
#[derive(Debug, Eq, PartialEq, Clone, Ord, Hash, PartialOrd, Serialize, Deserialize)]
pub enum ArgumentError {
MissingMandatoryFlag(String),
MissingMandatoryPositional(String),
@ -114,19 +100,12 @@ pub enum ArgumentError {
InvalidExternalWord,
}
#[derive(Debug, Eq, PartialEq, Clone, Ord, PartialOrd, Serialize, Deserialize)]
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Clone, Serialize, Deserialize, Hash)]
pub struct ShellError {
error: ProximateShellError,
cause: Option<Box<ProximateShellError>>,
}
impl ShellError {
#[allow(unused)]
pub(crate) fn tag(&self) -> Option<Tag> {
self.error.tag()
}
}
impl FormatDebug for ShellError {
fn fmt_debug(&self, f: &mut DebugFormatter, source: &str) -> fmt::Result {
self.error.fmt_debug(f, source)
@ -145,7 +124,7 @@ impl serde::de::Error for ShellError {
impl ShellError {
pub fn type_error(
expected: impl Into<String>,
actual: Tagged<impl Into<String>>,
actual: Spanned<impl Into<String>>,
) -> ShellError {
ProximateShellError::TypeError {
expected: expected.into(),
@ -154,6 +133,28 @@ impl ShellError {
.start()
}
pub fn missing_property(
subpath: Spanned<impl Into<String>>,
expr: Spanned<impl Into<String>>,
) -> ShellError {
ProximateShellError::MissingProperty {
subpath: subpath.into(),
expr: expr.into(),
}
.start()
}
pub fn invalid_integer_index(
subpath: Spanned<impl Into<String>>,
integer: impl Into<Span>,
) -> ShellError {
ProximateShellError::InvalidIntegerIndex {
subpath: subpath.into(),
integer: integer.into(),
}
.start()
}
pub fn untagged_runtime_error(error: impl Into<String>) -> ShellError {
ProximateShellError::UntaggedRuntimeError {
reason: error.into(),
@ -161,10 +162,10 @@ impl ShellError {
.start()
}
pub(crate) fn unexpected_eof(expected: impl Into<String>, tag: impl Into<Tag>) -> ShellError {
pub(crate) fn unexpected_eof(expected: impl Into<String>, span: impl Into<Span>) -> ShellError {
ProximateShellError::UnexpectedEof {
expected: expected.into(),
tag: tag.into(),
span: span.into(),
}
.start()
}
@ -172,34 +173,26 @@ impl ShellError {
pub(crate) fn range_error(
expected: impl Into<ExpectedRange>,
actual: &Tagged<impl fmt::Debug>,
operation: String,
operation: impl Into<String>,
) -> ShellError {
ProximateShellError::RangeError {
kind: expected.into(),
actual_kind: format!("{:?}", actual.item).tagged(actual.tag()),
operation,
actual_kind: format!("{:?}", actual.item).spanned(actual.span()),
operation: operation.into(),
}
.start()
}
pub(crate) fn syntax_error(problem: Tagged<impl Into<String>>) -> ShellError {
pub(crate) fn syntax_error(problem: Spanned<impl Into<String>>) -> ShellError {
ProximateShellError::SyntaxError {
problem: problem.map(|p| p.into()),
}
.start()
}
#[allow(unused)]
pub(crate) fn invalid_command(problem: impl Into<Tag>) -> ShellError {
ProximateShellError::InvalidCommand {
command: problem.into(),
}
.start()
}
pub(crate) fn coerce_error(
left: Tagged<impl Into<String>>,
right: Tagged<impl Into<String>>,
left: Spanned<impl Into<String>>,
right: Spanned<impl Into<String>>,
) -> ShellError {
ProximateShellError::CoerceError {
left: left.map(|l| l.into()),
@ -208,23 +201,21 @@ impl ShellError {
.start()
}
pub(crate) fn missing_value(tag: Option<Tag>, reason: impl Into<String>) -> ShellError {
pub(crate) fn missing_value(span: Option<Span>, reason: impl Into<String>) -> ShellError {
ProximateShellError::MissingValue {
tag,
span,
reason: reason.into(),
}
.start()
}
pub(crate) fn argument_error(
command: impl Into<String>,
command: Spanned<impl Into<String>>,
kind: ArgumentError,
tag: impl Into<Tag>,
) -> ShellError {
ProximateShellError::ArgumentError {
command: command.into(),
command: command.map(|c| c.into()),
error: kind,
tag: tag.into(),
}
.start()
}
@ -262,18 +253,14 @@ impl ShellError {
pub(crate) fn to_diagnostic(self) -> Diagnostic<Span> {
match self.error {
ProximateShellError::InvalidCommand { command } => {
Diagnostic::new(Severity::Error, "Invalid command")
.with_label(Label::new_primary(command.span))
}
ProximateShellError::MissingValue { tag, reason } => {
ProximateShellError::MissingValue { span, reason } => {
let mut d = Diagnostic::new(
Severity::Bug,
format!("Internal Error (missing value) :: {}", reason),
);
if let Some(tag) = tag {
d = d.with_label(Label::new_primary(tag.span));
if let Some(span) = span {
d = d.with_label(Label::new_primary(span));
}
d
@ -281,80 +268,79 @@ impl ShellError {
ProximateShellError::ArgumentError {
command,
error,
tag,
} => match error {
ArgumentError::InvalidExternalWord => Diagnostic::new(
Severity::Error,
format!("Invalid bare word for Nu command (did you intend to invoke an external command?)"))
.with_label(Label::new_primary(tag.span)),
.with_label(Label::new_primary(command.span)),
ArgumentError::MissingMandatoryFlag(name) => Diagnostic::new(
Severity::Error,
format!(
"{} requires {}{}",
Color::Cyan.paint(command),
Color::Cyan.paint(&command.item),
Color::Black.bold().paint("--"),
Color::Black.bold().paint(name)
),
)
.with_label(Label::new_primary(tag.span)),
.with_label(Label::new_primary(command.span)),
ArgumentError::MissingMandatoryPositional(name) => Diagnostic::new(
Severity::Error,
format!(
"{} requires {} parameter",
Color::Cyan.paint(command),
Color::Cyan.paint(&command.item),
Color::Green.bold().paint(name.clone())
),
)
.with_label(
Label::new_primary(tag.span).with_message(format!("requires {} parameter", name)),
Label::new_primary(command.span).with_message(format!("requires {} parameter", name)),
),
ArgumentError::MissingValueForName(name) => Diagnostic::new(
Severity::Error,
format!(
"{} is missing value for flag {}{}",
Color::Cyan.paint(command),
Color::Cyan.paint(&command.item),
Color::Black.bold().paint("--"),
Color::Black.bold().paint(name)
),
)
.with_label(Label::new_primary(tag.span)),
.with_label(Label::new_primary(command.span)),
},
ProximateShellError::TypeError {
expected,
actual:
Tagged {
Spanned {
item: Some(actual),
tag,
span,
},
} => Diagnostic::new(Severity::Error, "Type Error").with_label(
Label::new_primary(tag.span)
Label::new_primary(span)
.with_message(format!("Expected {}, found {}", expected, actual)),
),
ProximateShellError::TypeError {
expected,
actual:
Tagged {
Spanned {
item: None,
tag
span
},
} => Diagnostic::new(Severity::Error, "Type Error")
.with_label(Label::new_primary(tag.span).with_message(expected)),
.with_label(Label::new_primary(span).with_message(expected)),
ProximateShellError::UnexpectedEof {
expected, tag
expected, span
} => Diagnostic::new(Severity::Error, format!("Unexpected end of input"))
.with_label(Label::new_primary(tag.span).with_message(format!("Expected {}", expected))),
.with_label(Label::new_primary(span).with_message(format!("Expected {}", expected))),
ProximateShellError::RangeError {
kind,
operation,
actual_kind:
Tagged {
Spanned {
item,
tag
span
},
} => Diagnostic::new(Severity::Error, "Range Error").with_label(
Label::new_primary(tag.span).with_message(format!(
Label::new_primary(span).with_message(format!(
"Expected to convert {} to {} while {}, but it was out of range",
item,
kind.desc(),
@ -364,12 +350,12 @@ impl ShellError {
ProximateShellError::SyntaxError {
problem:
Tagged {
tag,
Spanned {
span,
item
},
} => Diagnostic::new(Severity::Error, "Syntax Error")
.with_label(Label::new_primary(tag.span).with_message(item)),
.with_label(Label::new_primary(span).with_message(item)),
ProximateShellError::MissingProperty { subpath, expr, .. } => {
let subpath = subpath.into_label();
@ -389,11 +375,26 @@ impl ShellError {
diag
}
ProximateShellError::InvalidIntegerIndex { subpath,integer } => {
let subpath = subpath.into_label();
let mut diag = Diagnostic::new(Severity::Error, "Invalid integer property");
match subpath {
Ok(label) => diag = diag.with_label(label),
Err(ty) => diag.message = format!("Invalid integer property (for {})", ty)
}
diag = diag.with_label(Label::new_secondary(integer).with_message("integer"));
diag
}
ProximateShellError::Diagnostic(diag) => diag.diagnostic,
ProximateShellError::CoerceError { left, right } => {
Diagnostic::new(Severity::Error, "Coercion error")
.with_label(Label::new_primary(left.tag().span).with_message(left.item))
.with_label(Label::new_secondary(right.tag().span).with_message(right.item))
.with_label(Label::new_primary(left.span).with_message(left.item))
.with_label(Label::new_secondary(right.span).with_message(right.item))
}
ProximateShellError::UntaggedRuntimeError { reason } => Diagnostic::new(Severity::Error, format!("Error: {}", reason))
@ -447,7 +448,7 @@ impl ShellError {
}
}
#[derive(Debug, Eq, PartialEq, Clone, Ord, PartialOrd, Serialize, Deserialize)]
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Serialize, Deserialize)]
pub enum ExpectedRange {
I8,
I16,
@ -461,12 +462,24 @@ pub enum ExpectedRange {
U128,
F32,
F64,
Usize,
Size,
BigInt,
BigDecimal,
Range { start: usize, end: usize },
}
impl From<Range<usize>> for ExpectedRange {
fn from(range: Range<usize>) -> Self {
ExpectedRange::Range {
start: range.start,
end: range.end,
}
}
}
impl ExpectedRange {
fn desc(&self) -> &'static str {
fn desc(&self) -> String {
match self {
ExpectedRange::I8 => "an 8-bit signed integer",
ExpectedRange::I16 => "a 16-bit signed integer",
@ -480,51 +493,54 @@ impl ExpectedRange {
ExpectedRange::U128 => "a 128-bit unsigned integer",
ExpectedRange::F32 => "a 32-bit float",
ExpectedRange::F64 => "a 64-bit float",
ExpectedRange::Usize => "an list index",
ExpectedRange::Size => "a list offset",
ExpectedRange::BigDecimal => "a decimal",
ExpectedRange::BigInt => "an integer",
ExpectedRange::Range { start, end } => return format!("{} to {}", start, end),
}
.to_string()
}
}
#[derive(Debug, Eq, PartialEq, Clone, Ord, PartialOrd, Serialize, Deserialize)]
#[derive(Debug, Eq, PartialEq, Clone, Ord, PartialOrd, Serialize, Deserialize, Hash)]
pub enum ProximateShellError {
SyntaxError {
problem: Tagged<String>,
problem: Spanned<String>,
},
UnexpectedEof {
expected: String,
tag: Tag,
},
InvalidCommand {
command: Tag,
span: Span,
},
TypeError {
expected: String,
actual: Tagged<Option<String>>,
actual: Spanned<Option<String>>,
},
MissingProperty {
subpath: Description,
expr: Description,
tag: Tag,
},
InvalidIntegerIndex {
subpath: Description,
integer: Span,
},
MissingValue {
tag: Option<Tag>,
span: Option<Span>,
reason: String,
},
ArgumentError {
command: String,
command: Spanned<String>,
error: ArgumentError,
tag: Tag,
},
RangeError {
kind: ExpectedRange,
actual_kind: Tagged<String>,
actual_kind: Spanned<String>,
operation: String,
},
Diagnostic(ShellDiagnostic),
CoerceError {
left: Tagged<String>,
right: Tagged<String>,
left: Spanned<String>,
right: Spanned<String>,
},
UntaggedRuntimeError {
reason: String,
@ -539,21 +555,22 @@ impl ProximateShellError {
}
}
pub(crate) fn tag(&self) -> Option<Tag> {
Some(match self {
ProximateShellError::SyntaxError { problem } => problem.tag(),
ProximateShellError::UnexpectedEof { tag, .. } => tag.clone(),
ProximateShellError::InvalidCommand { command } => command.clone(),
ProximateShellError::TypeError { actual, .. } => actual.tag.clone(),
ProximateShellError::MissingProperty { tag, .. } => tag.clone(),
ProximateShellError::MissingValue { tag, .. } => return tag.clone(),
ProximateShellError::ArgumentError { tag, .. } => tag.clone(),
ProximateShellError::RangeError { actual_kind, .. } => actual_kind.tag.clone(),
ProximateShellError::Diagnostic(..) => return None,
ProximateShellError::UntaggedRuntimeError { .. } => return None,
ProximateShellError::CoerceError { left, right } => left.tag.until(&right.tag),
})
}
// pub(crate) fn tag(&self) -> Option<Tag> {
// Some(match self {
// ProximateShellError::SyntaxError { problem } => problem.tag(),
// ProximateShellError::UnexpectedEof { tag, .. } => tag.clone(),
// ProximateShellError::InvalidCommand { command } => command.clone(),
// ProximateShellError::TypeError { actual, .. } => actual.tag.clone(),
// ProximateShellError::MissingProperty { tag, .. } => tag.clone(),
// ProximateShellError::MissingValue { tag, .. } => return tag.clone(),
// ProximateShellError::ArgumentError { tag, .. } => tag.clone(),
// ProximateShellError::RangeError { actual_kind, .. } => actual_kind.tag.clone(),
// ProximateShellError::InvalidIntegerIndex { integer, .. } => integer.into(),
// ProximateShellError::Diagnostic(..) => return None,
// ProximateShellError::UntaggedRuntimeError { .. } => return None,
// ProximateShellError::CoerceError { left, right } => left.tag.until(&right.tag),
// })
// }
}
impl FormatDebug for ProximateShellError {
@ -568,6 +585,23 @@ pub struct ShellDiagnostic {
pub(crate) diagnostic: Diagnostic<Span>,
}
impl std::hash::Hash for ShellDiagnostic {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.diagnostic.severity.hash(state);
self.diagnostic.code.hash(state);
self.diagnostic.message.hash(state);
for label in &self.diagnostic.labels {
label.span.hash(state);
label.message.hash(state);
match label.style {
language_reporting::LabelStyle::Primary => 0.hash(state),
language_reporting::LabelStyle::Secondary => 1.hash(state),
}
}
}
}
impl PartialEq for ShellDiagnostic {
fn eq(&self, _other: &ShellDiagnostic) -> bool {
false
@ -598,10 +632,10 @@ impl std::fmt::Display for ShellError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match &self.error {
ProximateShellError::MissingValue { .. } => write!(f, "MissingValue"),
ProximateShellError::InvalidCommand { .. } => write!(f, "InvalidCommand"),
ProximateShellError::TypeError { .. } => write!(f, "TypeError"),
ProximateShellError::UnexpectedEof { .. } => write!(f, "UnexpectedEof"),
ProximateShellError::RangeError { .. } => write!(f, "RangeError"),
ProximateShellError::InvalidIntegerIndex { .. } => write!(f, "InvalidIntegerIndex"),
ProximateShellError::SyntaxError { .. } => write!(f, "SyntaxError"),
ProximateShellError::MissingProperty { .. } => write!(f, "MissingProperty"),
ProximateShellError::ArgumentError { .. } => write!(f, "ArgumentError"),

View File

@ -1,5 +1,6 @@
use crate::data::base::Block;
use crate::errors::ArgumentError;
use crate::parser::hir::path::{ColumnPath, RawPathMember};
use crate::parser::{
hir::{self, Expression, RawExpression},
CommandRegistry, Text,
@ -62,9 +63,8 @@ pub(crate) fn evaluate_baseline_expr(
match &expr.item {
RawExpression::Literal(literal) => Ok(evaluate_literal(literal.tagged(tag), source)),
RawExpression::ExternalWord => Err(ShellError::argument_error(
"Invalid external word",
"Invalid external word".spanned(tag.span),
ArgumentError::InvalidExternalWord,
tag,
)),
RawExpression::FilePath(path) => Ok(Value::path(path.clone()).tagged(tag)),
RawExpression::Synthetic(hir::Synthetic::String(s)) => {
@ -82,14 +82,8 @@ pub(crate) fn evaluate_baseline_expr(
match left.compare(binary.op(), &*right) {
Ok(result) => Ok(Value::boolean(result).tagged(tag)),
Err((left_type, right_type)) => Err(ShellError::coerce_error(
left_type.tagged(Tag {
span: binary.left().span,
anchor: None,
}),
right_type.tagged(Tag {
span: binary.right().span,
anchor: None,
}),
left_type.spanned(binary.left().span),
right_type.spanned(binary.right().span),
)),
}
}
@ -110,13 +104,14 @@ pub(crate) fn evaluate_baseline_expr(
let value = evaluate_baseline_expr(path.head(), registry, scope, source)?;
let mut item = value;
for name in path.tail() {
let next = item.get_data_by_key(name);
for member in path.tail() {
let next = item.get_data_by_member(member);
match next {
None => {
Err(err) => {
let possibilities = item.data_descriptors();
if let RawPathMember::String(name) = &member.item {
let mut possible_matches: Vec<_> = possibilities
.iter()
.map(|x| (natural::distance::levenshtein_distance(x, &name), x))
@ -131,14 +126,11 @@ pub(crate) fn evaluate_baseline_expr(
&tag,
));
} else {
return Err(ShellError::labeled_error(
"Unknown column",
"row does not have this column",
&tag,
));
return Err(err);
}
}
Some(next) => {
}
Ok(next) => {
item = next.clone().item.tagged(&tag);
}
};
@ -152,6 +144,14 @@ pub(crate) fn evaluate_baseline_expr(
fn evaluate_literal(literal: Tagged<&hir::Literal>, source: &Text) -> Tagged<Value> {
let result = match literal.item {
hir::Literal::ColumnPath(path) => {
let members = path
.iter()
.map(|member| member.to_path_member(source))
.collect();
Value::Primitive(Primitive::ColumnPath(ColumnPath::new(members)))
}
hir::Literal::Number(int) => int.into(),
hir::Literal::Size(int, unit) => unit.compute(int),
hir::Literal::String(tag) => Value::string(tag.slice(source)),
@ -212,10 +212,12 @@ fn evaluate_external(
_source: &Text,
) -> Result<Tagged<Value>, ShellError> {
Err(ShellError::syntax_error(
"Unexpected external command".tagged(*external.name()),
"Unexpected external command".spanned(*external.name()),
))
}
fn evaluate_command(tag: Tag, _scope: &Scope, _source: &Text) -> Result<Tagged<Value>, ShellError> {
Err(ShellError::syntax_error("Unexpected command".tagged(tag)))
Err(ShellError::syntax_error(
"Unexpected command".spanned(tag.span),
))
}

View File

@ -21,7 +21,7 @@ impl EntriesView {
for desc in descs {
let value = value.get_data(&desc);
let formatted_value = value.borrow().format_leaf(None);
let formatted_value = value.borrow().format_leaf().plain_string(75);
entries.push((desc.clone(), formatted_value))
}
@ -39,7 +39,7 @@ impl RenderView for EntriesView {
let max_name_size: usize = self.entries.iter().map(|(n, _)| n.len()).max().unwrap();
for (name, value) in &self.entries {
println!("{:width$} : {}", name, value, width = max_name_size)
outln!("{:width$} : {}", name, value, width = max_name_size)
}
Ok(())

View File

@ -30,7 +30,7 @@ impl RenderView for GenericView<'_> {
}
b @ Value::Block(_) => {
let printed = b.format_leaf(None);
let printed = b.format_leaf().plain_string(host.width());
let view = EntriesView::from_value(&Value::string(printed));
view.render_view(host)?;
Ok(())

View File

@ -1,6 +1,7 @@
use crate::data::Value;
use crate::format::RenderView;
use crate::prelude::*;
use crate::traits::{DebugDocBuilder, DebugDocBuilder as b, PrettyDebug};
use derive_new::new;
use textwrap::fill;
@ -57,63 +58,33 @@ impl TableView {
let mut entries = vec![];
for (idx, value) in values.iter().enumerate() {
// let mut row: Vec<(String, &'static str)> = match value {
// Tagged {
// item: Value::Row(..),
// ..
// } => headers
// .iter()
// .enumerate()
// .map(|(i, d)| {
// let data = value.get_data(d);
// return (
// data.borrow().format_leaf(Some(&headers[i])),
// data.borrow().style_leaf(),
// );
// })
// .collect(),
// x => vec![(x.format_leaf(None), x.style_leaf())],
// };
let mut row: Vec<(String, &'static str)> = headers
let mut row: Vec<(DebugDocBuilder, &'static str)> = match value {
Tagged {
item: Value::Row(..),
..
} => headers
.iter()
.enumerate()
.map(|(i, d)| {
if d == "<value>" {
match value {
Tagged {
item: Value::Row(..),
..
} => (
Value::nothing().format_leaf(None),
Value::nothing().style_leaf(),
),
_ => (value.format_leaf(None), value.style_leaf()),
}
} else {
match value {
Tagged {
item: Value::Row(..),
..
} => {
let data = value.get_data(d);
(
data.borrow().format_leaf(Some(&headers[i])),
return (
data.borrow().format_for_column(&headers[i]),
data.borrow().style_leaf(),
)
}
_ => (
Value::nothing().format_leaf(None),
Value::nothing().style_leaf(),
),
}
}
);
})
.collect();
.collect(),
x => vec![(x.format_leaf(), x.style_leaf())],
};
if values.len() > 1 {
// Indices are black, bold, right-aligned:
row.insert(0, (format!("{}", (starting_idx + idx).to_string()), "Fdbr"));
row.insert(
0,
(
b::primitive(format!("{}", (starting_idx + idx).to_string())),
"Fdbr",
),
);
}
entries.push(row);
@ -125,10 +96,13 @@ impl TableView {
headers.insert(0, format!("#"));
}
// Different platforms want different amounts of buffer, not sure why
let termwidth = std::cmp::max(textwrap::termwidth(), 20);
for head in 0..headers.len() {
let mut current_col_max = 0;
for row in 0..values.len() {
let value_length = entries[row][head].0.chars().count();
let value_length = entries[row][head].0.plain_string(termwidth).chars().count();
if value_length > current_col_max {
current_col_max = value_length;
}
@ -140,9 +114,6 @@ impl TableView {
));
}
// Different platforms want different amounts of buffer, not sure why
let termwidth = std::cmp::max(textwrap::termwidth(), 20);
// Make sure we have enough space for the columns we have
let max_num_of_columns = termwidth / 10;
@ -155,7 +126,7 @@ impl TableView {
headers.push("...".to_string());
for row in 0..entries.len() {
entries[row].push(("...".to_string(), "c")); // ellipsis is centred
entries[row].push((b::description("..."), "c")); // ellipsis is centred
}
}
@ -227,20 +198,54 @@ impl TableView {
99999
};
let mut out_entries: Vec<Vec<(String, &'static str)>> = Vec::with_capacity(entries.len());
for row in entries.iter() {
let mut out_row = Vec::with_capacity(row.len());
for _ in row {
out_row.push((String::new(), "Fdbr"));
}
out_entries.push(out_row);
}
// Wrap cells as needed
for head in 0..headers.len() {
if max_per_column[head] > max_naive_column_width {
if true_width(&headers[head]) > max_column_width {
headers[head] = fill(&headers[head], max_column_width);
for row in 0..entries.len() {
entries[row][head].0 = fill(&entries[row][head].0, max_column_width);
}
for (i, row) in entries.iter().enumerate() {
let column = &row[head].0;
let column = column.plain_string(max_column_width);
out_entries[i][head] = (column, row[head].1);
}
} else {
for (i, row) in entries.iter().enumerate() {
let column = &row[head].0;
let column = column.plain_string(max_column_width);
out_entries[i][head] = (column, row[head].1);
}
}
}
Some(TableView { headers, entries })
Some(TableView {
headers,
entries: out_entries,
})
}
}
fn true_width(string: &str) -> usize {
let stripped = console::strip_ansi_codes(string);
stripped.lines().map(|line| line.len()).max().unwrap_or(0)
}
impl RenderView for TableView {
fn render_view(&self, host: &mut dyn Host) -> Result<(), ShellError> {
if self.entries.len() == 0 {

187
src/fuzzysearch.rs Normal file
View File

@ -0,0 +1,187 @@
use ansi_term::{ANSIString, ANSIStrings, Colour, Style};
#[cfg(feature = "crossterm")]
use crossterm::{cursor, terminal, ClearType, InputEvent, KeyEvent, RawScreen};
use std::io::Write;
use sublime_fuzzy::best_match;
pub enum SelectionResult {
Selected(String),
Edit(String),
NoSelection,
}
pub fn interactive_fuzzy_search(lines: &Vec<&str>, max_results: usize) -> SelectionResult {
#[derive(PartialEq)]
enum State {
Selecting,
Quit,
Selected(String),
Edit(String),
}
let mut state = State::Selecting;
#[cfg(feature = "crossterm")]
{
if let Ok(_raw) = RawScreen::into_raw_mode() {
// User input for search
let mut searchinput = String::new();
let mut selected = 0;
let mut cursor = cursor();
let _ = cursor.hide();
let input = crossterm::input();
let mut sync_stdin = input.read_sync();
while state == State::Selecting {
let mut selected_lines = fuzzy_search(&searchinput, &lines, max_results);
let num_lines = selected_lines.len();
paint_selection_list(&selected_lines, selected);
if let Some(ev) = sync_stdin.next() {
match ev {
InputEvent::Keyboard(k) => match k {
KeyEvent::Esc | KeyEvent::Ctrl('c') => {
state = State::Quit;
}
KeyEvent::Up => {
if selected > 0 {
selected -= 1;
}
}
KeyEvent::Down => {
if selected + 1 < selected_lines.len() {
selected += 1;
}
}
KeyEvent::Char('\n') => {
state = if selected_lines.len() > 0 {
State::Selected(selected_lines.remove(selected).text)
} else {
State::Edit("".to_string())
};
}
KeyEvent::Char('\t') | KeyEvent::Right => {
state = if selected_lines.len() > 0 {
State::Edit(selected_lines.remove(selected).text)
} else {
State::Edit("".to_string())
};
}
KeyEvent::Char(ch) => {
searchinput.push(ch);
selected = 0;
}
KeyEvent::Backspace => {
searchinput.pop();
selected = 0;
}
_ => {}
},
_ => {}
}
}
if num_lines > 0 {
cursor.move_up(num_lines as u16);
}
}
let (_x, y) = cursor.pos();
let _ = cursor.goto(0, y - 1);
let _ = cursor.show();
let _ = RawScreen::disable_raw_mode();
}
terminal().clear(ClearType::FromCursorDown).unwrap();
}
match state {
State::Selected(line) => SelectionResult::Selected(line),
State::Edit(line) => SelectionResult::Edit(line),
_ => SelectionResult::NoSelection,
}
}
pub struct Match {
text: String,
char_matches: Vec<(usize, usize)>,
}
pub fn fuzzy_search(searchstr: &str, lines: &Vec<&str>, max_results: usize) -> Vec<Match> {
if searchstr.is_empty() {
return lines
.iter()
.take(max_results)
.map(|line| Match {
text: line.to_string(),
char_matches: Vec::new(),
})
.collect();
}
let mut matches = lines
.iter()
.enumerate()
.map(|(idx, line)| (idx, best_match(&searchstr, line)))
.filter(|(_i, m)| m.is_some())
.map(|(i, m)| (i, m.unwrap()))
.collect::<Vec<_>>();
matches.sort_by(|a, b| b.1.score().cmp(&a.1.score()));
let results: Vec<Match> = matches
.iter()
.take(max_results)
.map(|(i, m)| Match {
text: lines[*i].to_string(),
char_matches: m.continuous_matches(),
})
.collect();
results
}
#[cfg(feature = "crossterm")]
fn highlight(textmatch: &Match, normal: Style, highlighted: Style) -> Vec<ANSIString> {
let text = &textmatch.text;
let mut ansi_strings = vec![];
let mut idx = 0;
for (match_idx, len) in &textmatch.char_matches {
ansi_strings.push(normal.paint(&text[idx..*match_idx]));
idx = match_idx + len;
ansi_strings.push(highlighted.paint(&text[*match_idx..idx]));
}
if idx < text.len() {
ansi_strings.push(normal.paint(&text[idx..text.len()]));
}
ansi_strings
}
#[cfg(feature = "crossterm")]
fn paint_selection_list(lines: &Vec<Match>, selected: usize) {
let terminal = terminal();
let size = terminal.terminal_size();
let width = size.0 as usize;
let cursor = cursor();
let (_x, y) = cursor.pos();
for (i, line) in lines.iter().enumerate() {
let _ = cursor.goto(0, y + (i as u16));
let (style, highlighted) = if selected == i {
(Colour::White.normal(), Colour::Cyan.normal())
} else {
(Colour::White.dimmed(), Colour::Cyan.normal())
};
let mut ansi_strings = highlight(line, style, highlighted);
for _ in line.text.len()..width {
ansi_strings.push(style.paint(' '.to_string()));
}
outln!("{}", ANSIStrings(&ansi_strings));
}
let _ = cursor.goto(0, y + (lines.len() as u16));
print!(
"{}",
Colour::Blue.paint("[ESC to quit, Enter to execute, Tab to edit]")
);
let _ = std::io::stdout().flush();
// Clear additional lines from previous selection
terminal.clear(ClearType::FromCursorDown).unwrap();
}
#[test]
fn fuzzy_match() {
let matches = fuzzy_search("cb", &vec!["abc", "cargo build"], 1);
assert_eq!(matches[0].text, "cargo build");
}

View File

@ -15,6 +15,7 @@ mod env;
mod errors;
mod evaluate;
mod format;
mod fuzzysearch;
mod git;
mod parser;
mod plugin;
@ -26,18 +27,19 @@ mod utils;
pub use crate::commands::command::{CallInfo, ReturnSuccess, ReturnValue};
pub use crate::context::AnchorLocation;
pub use crate::env::host::BasicHost;
pub use crate::parser::hir::path::{ColumnPath, PathMember, RawPathMember};
pub use crate::parser::hir::SyntaxShape;
pub use crate::parser::parse::token_tree_builder::TokenTreeBuilder;
pub use crate::plugin::{serve_plugin, Plugin};
pub use crate::traits::{DebugFormatter, FormatDebug, ToDebug};
pub use crate::traits::{DebugFormatter, FormatDebug, ShellTypeName, SpannedTypeName, ToDebug};
pub use crate::utils::{did_you_mean, AbsoluteFile, AbsolutePath, RelativePath};
pub use cli::cli;
pub use data::base::{Primitive, Value};
pub use data::config::{config_path, APP_INFO};
pub use data::dict::{Dictionary, TaggedDictBuilder, TaggedListBuilder};
pub use data::meta::{
tag_for_tagged_list, HasFallibleSpan, HasSpan, Span, Spanned, SpannedItem, Tag, Tagged,
TaggedItem,
span_for_spanned_list, tag_for_tagged_list, HasFallibleSpan, HasSpan, Span, Spanned,
SpannedItem, Tag, Tagged, TaggedItem,
};
pub use errors::{CoerceInto, ShellError};
pub use num_traits::cast::ToPrimitive;

View File

@ -1,3 +1,4 @@
pub(crate) mod debug;
pub(crate) mod deserializer;
pub(crate) mod hir;
pub(crate) mod parse;

51
src/parser/debug.rs Normal file
View File

@ -0,0 +1,51 @@
use crate::traits::ShellAnnotation;
use pretty::{Render, RenderAnnotated};
use std::io;
use termcolor::WriteColor;
pub struct TermColored<'a, W> {
color_stack: Vec<ShellAnnotation>,
upstream: &'a mut W,
}
impl<'a, W> TermColored<'a, W> {
pub fn new(upstream: &'a mut W) -> TermColored<'a, W> {
TermColored {
color_stack: Vec::new(),
upstream,
}
}
}
impl<'a, W> Render for TermColored<'a, W>
where
W: io::Write,
{
type Error = io::Error;
fn write_str(&mut self, s: &str) -> io::Result<usize> {
self.upstream.write(s.as_bytes())
}
fn write_str_all(&mut self, s: &str) -> io::Result<()> {
self.upstream.write_all(s.as_bytes())
}
}
impl<'a, W> RenderAnnotated<ShellAnnotation> for TermColored<'a, W>
where
W: WriteColor,
{
fn push_annotation(&mut self, ann: &ShellAnnotation) -> Result<(), Self::Error> {
self.color_stack.push(*ann);
self.upstream.set_color(&(*ann).into())
}
fn pop_annotation(&mut self) -> Result<(), Self::Error> {
self.color_stack.pop();
match self.color_stack.last() {
Some(previous) => self.upstream.set_color(&(*previous).into()),
None => self.upstream.reset(),
}
}
}

View File

@ -1,4 +1,5 @@
use crate::prelude::*;
use crate::ColumnPath;
use log::trace;
use serde::de;
use std::path::PathBuf;
@ -97,7 +98,10 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut ConfigDeserializer<'de> {
item: Value::Primitive(Primitive::Nothing),
..
} => visitor.visit_bool(false),
other => Err(ShellError::type_error("Boolean", other.tagged_type_name())),
other => Err(ShellError::type_error(
"Boolean",
other.type_name().spanned(other.span()),
)),
}
}
fn deserialize_i8<V>(self, _visitor: V) -> Result<V::Value, Self::Error>
@ -242,7 +246,10 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut ConfigDeserializer<'de> {
let de = SeqDeserializer::new(&mut self, items.into_iter());
visitor.visit_seq(de)
}
(other, tag) => Err(ShellError::type_error("Vec", other.type_name().tagged(tag))),
(other, tag) => Err(ShellError::type_error(
"Vec",
other.type_name().spanned(tag),
)),
}
}
fn deserialize_tuple<V>(mut self, len: usize, visitor: V) -> Result<V::Value, Self::Error>
@ -263,7 +270,7 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut ConfigDeserializer<'de> {
}
(other, tag) => Err(ShellError::type_error(
"Tuple",
other.type_name().tagged(tag),
other.type_name().spanned(tag),
)),
}
}
@ -328,7 +335,8 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut ConfigDeserializer<'de> {
let tagged_val_name = std::any::type_name::<Tagged<Value>>();
trace!(
"type_name={} tagged_val_name={}",
"name={} type_name={} tagged_val_name={}",
name,
type_name,
tagged_val_name
);
@ -343,11 +351,33 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut ConfigDeserializer<'de> {
item: Value::Block(block),
..
} => block,
other => return Err(ShellError::type_error("Block", other.tagged_type_name())),
other => {
return Err(ShellError::type_error(
"Block",
other.type_name().spanned(other.span()),
))
}
};
return visit::<value::Block, _>(block, name, fields, visitor);
}
if name == "ColumnPath" {
let path = match value.val {
Tagged {
item: Value::Primitive(Primitive::ColumnPath(path)),
..
} => path,
other => {
return Err(ShellError::type_error(
"column path",
other.type_name().spanned(other.span()),
))
}
};
return visit::<ColumnPath, _>(path, name, fields, visitor);
}
trace!("Extracting {:?} for {:?}", value.val, type_name);
let tag = value.val.tag();
@ -376,7 +406,12 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut ConfigDeserializer<'de> {
..
} => visit::<Tagged<String>, _>(string.tagged(tag), name, fields, visitor),
other => return Err(ShellError::type_error(name, other.tagged_type_name())),
other => {
return Err(ShellError::type_error(
name,
other.type_name().spanned(other.span()),
))
}
}
}
fn deserialize_enum<V>(

View File

@ -7,6 +7,8 @@ pub(crate) mod path;
pub(crate) mod syntax_shape;
pub(crate) mod tokens_iterator;
use crate::parser::hir::path::PathMember;
use crate::parser::hir::syntax_shape::Member;
use crate::parser::{registry, Operator, Unit};
use crate::prelude::*;
use derive_new::new;
@ -178,24 +180,30 @@ impl Expression {
RawExpression::Literal(Literal::String(inner.into())).spanned(outer.into())
}
pub(crate) fn column_path(members: Vec<Member>, span: impl Into<Span>) -> Expression {
RawExpression::Literal(Literal::ColumnPath(members)).spanned(span.into())
}
pub(crate) fn path(
head: Expression,
tail: Vec<Spanned<impl Into<String>>>,
tail: Vec<impl Into<PathMember>>,
span: impl Into<Span>,
) -> Expression {
let tail = tail.into_iter().map(|t| t.map(|s| s.into())).collect();
let tail = tail.into_iter().map(|t| t.into()).collect();
RawExpression::Path(Box::new(Path::new(head, tail))).spanned(span.into())
}
pub(crate) fn dot_member(head: Expression, next: Spanned<impl Into<String>>) -> Expression {
pub(crate) fn dot_member(head: Expression, next: impl Into<PathMember>) -> Expression {
let Spanned { item, span } = head;
let next = next.into();
let new_span = head.span.until(next.span);
match item {
RawExpression::Path(path) => {
let (head, mut tail) = path.parts();
tail.push(next.map(|i| i.into()));
tail.push(next);
Expression::path(head, tail, new_span)
}
@ -241,10 +249,6 @@ impl Expression {
pub(crate) fn it_variable(inner: impl Into<Span>, outer: impl Into<Span>) -> Expression {
RawExpression::Variable(Variable::It(inner.into())).spanned(outer)
}
pub(crate) fn tagged_type_name(&self) -> Tagged<&'static str> {
self.item.type_name().tagged(self.span)
}
}
impl FormatDebug for Spanned<RawExpression> {
@ -301,6 +305,7 @@ pub enum Literal {
Size(Number, Unit),
String(Span),
GlobPattern(String),
ColumnPath(Vec<Member>),
Bare,
}
@ -318,6 +323,7 @@ impl std::fmt::Display for Tagged<&Literal> {
Literal::Number(number) => write!(f, "{}", number),
Literal::Size(number, unit) => write!(f, "{}{}", number, unit.as_str()),
Literal::String(_) => write!(f, "String{{ {}..{} }}", span.start(), span.end()),
Literal::ColumnPath(_) => write!(f, "ColumnPath"),
Literal::GlobPattern(_) => write!(f, "Glob{{ {}..{} }}", span.start(), span.end()),
Literal::Bare => write!(f, "Bare{{ {}..{} }}", span.start(), span.end()),
}
@ -330,6 +336,15 @@ impl FormatDebug for Spanned<&Literal> {
Literal::Number(..) => f.say_str("number", self.span.slice(source)),
Literal::Size(..) => f.say_str("size", self.span.slice(source)),
Literal::String(..) => f.say_str("string", self.span.slice(source)),
Literal::ColumnPath(path) => f.say_block("column path", |f| {
write!(f, "[ ")?;
for member in path {
write!(f, "{} ", member.debug(source))?;
}
write!(f, "]")
}),
Literal::GlobPattern(..) => f.say_str("glob", self.span.slice(source)),
Literal::Bare => f.say_str("word", self.span.slice(source)),
}
@ -342,6 +357,7 @@ impl Literal {
Literal::Number(..) => "number",
Literal::Size(..) => "size",
Literal::String(..) => "string",
Literal::ColumnPath(..) => "column path",
Literal::Bare => "string",
Literal::GlobPattern(_) => "pattern",
}

View File

@ -1,9 +1,10 @@
use crate::commands::classified::InternalCommand;
use crate::commands::ClassifiedCommand;
use crate::env::host::BasicHost;
use crate::parser::hir::syntax_shape::*;
use crate::parser::hir::TokensIterator;
use crate::parser::hir::{self, named::NamedValue, NamedArguments};
use crate::parser::hir::{
self, named::NamedValue, path::PathMember, syntax_shape::*, NamedArguments,
};
use crate::parser::parse::token_tree_builder::{CurriedToken, TokenTreeBuilder as b};
use crate::parser::TokenNode;
use crate::{HasSpan, Span, SpannedItem, Tag, Text};
@ -28,7 +29,7 @@ fn test_parse_path() {
let bare = tokens[2].expect_bare();
hir::Expression::path(
hir::Expression::it_variable(inner_var, outer_var),
vec!["cpu".spanned(bare)],
vec![PathMember::string("cpu", bare)],
outer_var.until(bare),
)
},
@ -50,7 +51,10 @@ fn test_parse_path() {
hir::Expression::path(
hir::Expression::variable(inner_var, outer_var),
vec!["amount".spanned(amount), "max ghz".spanned(outer_max_ghz)],
vec![
PathMember::string("amount", amount),
PathMember::string("max ghz", outer_max_ghz),
],
outer_var.until(outer_max_ghz),
)
},
@ -66,8 +70,6 @@ fn test_parse_command() {
let bare = tokens[0].expect_bare();
let pat = tokens[2].expect_pattern();
eprintln!("{:?} {:?} {:?}", bare, pat, bare.until(pat));
let mut map = IndexMap::new();
map.insert("full".to_string(), NamedValue::AbsentSwitch);

View File

@ -217,7 +217,7 @@ impl ExpandExpression for ExternalHeadShape {
| AtomicToken::Pipeline { .. } => {
return Err(ParseError::mismatch(
"external command name",
atom.tagged_type_name(),
"pipeline".spanned(atom.span),
))
}
AtomicToken::ExternalCommand { command } => {
@ -293,7 +293,7 @@ impl ExpandExpression for ExternalContinuationShape {
| AtomicToken::Pipeline { .. } => {
return Err(ParseError::mismatch(
"external argument",
atom.tagged_type_name(),
"pipeline".spanned(atom.span),
))
}
}),

View File

@ -1,10 +1,107 @@
use crate::parser::hir::Expression;
use crate::prelude::*;
use crate::traits::{DebugDocBuilder as b, PrettyDebug};
use derive_new::new;
use getset::{Getters, MutGetters};
use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
pub enum RawPathMember {
String(String),
Int(BigInt),
}
pub type PathMember = Spanned<RawPathMember>;
impl PrettyDebug for &PathMember {
fn pretty_debug(&self) -> DebugDocBuilder {
match &self.item {
RawPathMember::String(string) => b::primitive(format!("{:?}", string)),
RawPathMember::Int(int) => b::primitive(format!("{}", int)),
}
}
}
#[derive(
Debug, Hash, Serialize, Deserialize, Ord, PartialOrd, Eq, PartialEq, Getters, Clone, new,
)]
pub struct ColumnPath {
#[get = "pub"]
members: Vec<PathMember>,
}
impl ColumnPath {
pub fn iter(&self) -> impl Iterator<Item = &PathMember> {
self.members.iter()
}
pub fn split_last(&self) -> (&PathMember, &[PathMember]) {
self.members.split_last().unwrap()
}
}
impl PrettyDebug for ColumnPath {
fn pretty_debug(&self) -> DebugDocBuilder {
let members: Vec<DebugDocBuilder> = self
.members
.iter()
.map(|member| member.pretty_debug())
.collect();
b::delimit(
"(",
b::description("path") + b::equals() + b::intersperse(members, b::space()),
")",
)
.nest()
}
}
impl FormatDebug for ColumnPath {
fn fmt_debug(&self, f: &mut DebugFormatter, source: &str) -> fmt::Result {
self.members.fmt_debug(f, source)
}
}
impl HasFallibleSpan for ColumnPath {
fn maybe_span(&self) -> Option<Span> {
if self.members.len() == 0 {
None
} else {
Some(span_for_spanned_list(self.members.iter().map(|m| m.span)))
}
}
}
impl fmt::Display for RawPathMember {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
RawPathMember::String(string) => write!(f, "{}", string),
RawPathMember::Int(int) => write!(f, "{}", int),
}
}
}
impl PathMember {
pub fn string(string: impl Into<String>, span: impl Into<Span>) -> PathMember {
RawPathMember::String(string.into()).spanned(span.into())
}
pub fn int(int: impl Into<BigInt>, span: impl Into<Span>) -> PathMember {
RawPathMember::Int(int.into()).spanned(span.into())
}
}
impl FormatDebug for PathMember {
fn fmt_debug(&self, f: &mut DebugFormatter, _source: &str) -> fmt::Result {
match &self.item {
RawPathMember::String(string) => f.say_str("member", &string),
RawPathMember::Int(int) => f.say_block("member", |f| write!(f, "{}", int)),
}
}
}
#[derive(
Debug,
Clone,
@ -23,7 +120,7 @@ use std::fmt;
pub struct Path {
head: Expression,
#[get_mut = "pub(crate)"]
tail: Vec<Spanned<String>>,
tail: Vec<PathMember>,
}
impl fmt::Display for Path {
@ -39,7 +136,7 @@ impl fmt::Display for Path {
}
impl Path {
pub(crate) fn parts(self) -> (Expression, Vec<Spanned<String>>) {
pub(crate) fn parts(self) -> (Expression, Vec<PathMember>) {
(self.head, self.tail)
}
}

View File

@ -31,7 +31,7 @@ pub(crate) use self::expression::string::StringShape;
pub(crate) use self::expression::unit::UnitShape;
pub(crate) use self::expression::variable_path::{
ColorableDotShape, ColumnPathShape, DotShape, ExpressionContinuation,
ExpressionContinuationShape, MemberShape, PathTailShape, VariablePathShape,
ExpressionContinuationShape, Member, MemberShape, PathTailShape, VariablePathShape,
};
pub(crate) use self::expression::{continue_expression, AnyExpressionShape};
pub(crate) use self::flat_shape::FlatShape;
@ -138,15 +138,15 @@ impl FallibleColorSyntax for SyntaxShape {
impl ExpandExpression for SyntaxShape {
fn name(&self) -> &'static str {
match self {
SyntaxShape::Any => "any",
SyntaxShape::Int => "integer",
SyntaxShape::String => "string",
SyntaxShape::Member => "column name",
SyntaxShape::ColumnPath => "column path",
SyntaxShape::Number => "number",
SyntaxShape::Path => "file path",
SyntaxShape::Pattern => "glob pattern",
SyntaxShape::Block => "block",
SyntaxShape::Any => "shape[any]",
SyntaxShape::Int => "shape[integer]",
SyntaxShape::String => "shape[string]",
SyntaxShape::Member => "shape[column name]",
SyntaxShape::ColumnPath => "shape[column path]",
SyntaxShape::Number => "shape[number]",
SyntaxShape::Path => "shape[file path]",
SyntaxShape::Pattern => "shape[glob pattern]",
SyntaxShape::Block => "shape[block]",
}
}
@ -165,13 +165,12 @@ impl ExpandExpression for SyntaxShape {
}
SyntaxShape::ColumnPath => {
let column_path = expand_syntax(&ColumnPathShape, token_nodes, context)?;
let Tagged { item: members, tag } = column_path.path();
Ok(hir::Expression::list(
members.into_iter().map(|s| s.to_expr()).collect(),
let Tagged {
item: column_path,
tag,
))
} = column_path;
Ok(hir::Expression::column_path(column_path, tag.span))
}
SyntaxShape::Number => expand_expr(&NumberShape, token_nodes, context),
SyntaxShape::Path => expand_expr(&FilePathShape, token_nodes, context),
@ -587,7 +586,7 @@ impl ExpandSyntax for BarePathShape {
type Output = Span;
fn name(&self) -> &'static str {
"shorthand path"
"bare path"
}
fn expand_syntax<'a, 'b>(
@ -637,7 +636,7 @@ impl FallibleColorSyntax for BareShape {
}
// otherwise, fail
other => Err(ParseError::mismatch("word", other.tagged_type_name())),
other => Err(ParseError::mismatch("word", other.spanned_type_name())),
})
.map_err(|err| err.into())
}
@ -666,7 +665,10 @@ impl FallibleColorSyntax for BareShape {
}) => Ok(span),
// otherwise, fail
other => Err(ParseError::mismatch("word", other.tagged_type_name())),
other => Err(ParseError::mismatch(
"word",
other.type_name().spanned(other.span()),
)),
})?;
token_nodes.color_shape((*input).spanned(*span));
@ -698,7 +700,10 @@ impl ExpandSyntax for BareShape {
Ok(span.spanned_string(context.source))
}
other => Err(ParseError::mismatch("word", other.tagged_type_name())),
other => Err(ParseError::mismatch(
"word",
other.type_name().spanned(other.span()),
)),
}
}
}
@ -986,7 +991,7 @@ impl FallibleColorSyntax for CommandHeadShape {
// Otherwise, we're not actually looking at a command
_ => Err(ShellError::syntax_error(
"No command at the head".tagged(atom.span),
"No command at the head".spanned(atom.span),
)),
}
})
@ -1043,7 +1048,7 @@ impl FallibleColorSyntax for CommandHeadShape {
// Otherwise, we're not actually looking at a command
_ => Err(ShellError::syntax_error(
"No command at the head".tagged(atom.span),
"No command at the head".spanned(atom.span),
)),
}
})
@ -1081,7 +1086,7 @@ impl ExpandSyntax for CommandHeadShape {
_ => {
return Err(ShellError::type_error(
"command head2",
token.type_name().tagged(token_span),
token.type_name().spanned(token_span),
))
}
})
@ -1116,9 +1121,10 @@ impl ExpandSyntax for ClassifiedCommandShape {
let head = expand_syntax(&CommandHeadShape, iterator, context)?;
match &head {
CommandSignature::Expression(expr) => {
Err(ParseError::mismatch("command", expr.tagged_type_name()))
}
CommandSignature::Expression(expr) => Err(ParseError::mismatch(
"command",
expr.type_name().spanned(expr.span),
)),
// If the command starts with `^`, treat it as an external command no matter what
CommandSignature::External(name) => {
@ -1276,7 +1282,7 @@ impl ExpandExpression for InternalCommandHeadShape {
node => {
return Err(ParseError::mismatch(
"command head",
node.tagged_type_name(),
node.type_name().spanned(node.span()),
))
}
};
@ -1294,7 +1300,7 @@ pub(crate) struct SingleError<'token> {
impl<'token> SingleError<'token> {
pub(crate) fn error(&self) -> ParseError {
ParseError::mismatch(self.expected, self.node.type_name().tagged(self.node.span))
ParseError::mismatch(self.expected, self.node.type_name().spanned(self.node.span))
}
}
@ -1313,7 +1319,10 @@ fn parse_single_node<'a, 'b, T>(
},
),
other => Err(ParseError::mismatch(expected, other.tagged_type_name())),
other => Err(ParseError::mismatch(
expected,
other.type_name().spanned(other.span()),
)),
})
}
@ -1334,7 +1343,12 @@ fn parse_single_node_skipping_ws<'a, 'b, T>(
},
)?,
other => return Err(ShellError::type_error(expected, other.tagged_type_name())),
other => {
return Err(ShellError::type_error(
expected,
other.type_name().spanned(other.span()),
))
}
};
peeked.commit();
@ -1429,7 +1443,12 @@ impl ExpandSyntax for WhitespaceShape {
let span = match peeked.node {
TokenNode::Whitespace(tag) => *tag,
other => return Err(ParseError::mismatch("whitespace", other.tagged_type_name())),
other => {
return Err(ParseError::mismatch(
"whitespace",
other.type_name().spanned(other.span()),
))
}
};
peeked.commit();
@ -1462,7 +1481,10 @@ impl<T: ExpandExpression> ExpandExpression for SpacedExpression<T> {
expand_expr(&self.inner, token_nodes, context)
}
other => Err(ParseError::mismatch("whitespace", other.tagged_type_name())),
other => Err(ParseError::mismatch(
"whitespace",
other.type_name().spanned(other.span()),
)),
}
}
}
@ -1590,7 +1612,7 @@ impl FallibleColorSyntax for SpaceShape {
other => Err(ShellError::type_error(
"whitespace",
other.tagged_type_name(),
other.spanned_type_name(),
)),
}
}
@ -1622,7 +1644,7 @@ impl FallibleColorSyntax for SpaceShape {
other => Err(ShellError::type_error(
"whitespace",
other.tagged_type_name(),
other.type_name().spanned(other.span()),
)),
}
}

View File

@ -11,7 +11,6 @@ use crate::parser::{
},
hir::tokens_iterator::TokensIterator,
parse::token_tree::Delimiter,
RawToken, TokenNode,
};
use crate::{Span, Spanned, SpannedItem};
@ -381,8 +380,11 @@ impl FallibleColorSyntax for ShorthandHeadShape {
_context: &ExpandContext,
shapes: &mut Vec<Spanned<FlatShape>>,
) -> Result<(), ShellError> {
use crate::parser::parse::token_tree::TokenNode;
use crate::parser::parse::tokens::RawToken;
// A shorthand path must not be at EOF
let peeked = token_nodes.peek_non_ws().not_eof("shorthand path")?;
let peeked = token_nodes.peek_non_ws().not_eof("shorthand path head")?;
match peeked.node {
// If the head of a shorthand path is a bare token, it expands to `$it.bare`
@ -407,7 +409,7 @@ impl FallibleColorSyntax for ShorthandHeadShape {
other => Err(ShellError::type_error(
"shorthand head",
other.tagged_type_name(),
other.spanned_type_name(),
)),
}
}
@ -427,7 +429,7 @@ impl FallibleColorSyntax for ShorthandHeadShape {
shapes: &mut Vec<Spanned<FlatShape>>,
) -> Result<(), ShellError> {
// A shorthand path must not be at EOF
let peeked = token_nodes.peek_non_ws().not_eof("shorthand path")?;
let peeked = token_nodes.peek_non_ws().not_eof("shorthand path head")?;
match peeked.node {
// If the head of a shorthand path is a bare token, it expands to `$it.bare`
@ -468,56 +470,14 @@ impl ExpandExpression for ShorthandHeadShape {
token_nodes: &'b mut TokensIterator<'a>,
context: &ExpandContext,
) -> Result<hir::Expression, ParseError> {
// A shorthand path must not be at EOF
let peeked = token_nodes.peek_non_ws().not_eof("shorthand path")?;
match peeked.node {
// If the head of a shorthand path is a bare token, it expands to `$it.bare`
TokenNode::Token(Spanned {
item: RawToken::Bare,
span,
}) => {
// Commit the peeked token
peeked.commit();
let head = expand_syntax(&MemberShape, token_nodes, context)?;
let head = head.to_path_member(context.source);
// Synthesize an `$it` expression
let it = synthetic_it();
let span = head.span;
// Make a path out of `$it` and the bare token as a member
Ok(hir::Expression::path(
it,
vec![span.spanned_string(context.source)],
*span,
))
}
// If the head of a shorthand path is a string, it expands to `$it."some string"`
TokenNode::Token(Spanned {
item: RawToken::String(inner),
span: outer,
}) => {
// Commit the peeked token
peeked.commit();
// Synthesize an `$it` expression
let it = synthetic_it();
// Make a path out of `$it` and the bare token as a member
Ok(hir::Expression::path(
it,
vec![inner.string(context.source).spanned(*outer)],
*outer,
))
}
// Any other token is not a valid bare head
other => {
return Err(ParseError::mismatch(
"shorthand path",
other.tagged_type_name(),
))
}
}
Ok(hir::Expression::path(it, vec![head], span))
}
}

View File

@ -392,7 +392,7 @@ impl FallibleColorSyntax for BareTailShape {
Ok(())
} else {
Err(ShellError::syntax_error(
"No tokens matched BareTailShape".tagged_unknown(),
"No tokens matched BareTailShape".spanned_unknown(),
))
}
}
@ -446,7 +446,7 @@ impl FallibleColorSyntax for BareTailShape {
Ok(())
} else {
Err(ShellError::syntax_error(
"No tokens matched BareTailShape".tagged_unknown(),
"No tokens matched BareTailShape".spanned_unknown(),
))
}
}

View File

@ -11,7 +11,7 @@ use crate::parser::{
use crate::prelude::*;
use crate::{Span, Spanned};
#[derive(Debug)]
#[derive(Debug, Clone)]
pub enum AtomicToken<'tokens> {
Eof {
span: Span,
@ -83,6 +83,34 @@ pub enum AtomicToken<'tokens> {
},
}
impl<'tokens> ShellTypeName for AtomicToken<'tokens> {
fn type_name(&self) -> &'static str {
match &self {
AtomicToken::Eof { .. } => "eof",
AtomicToken::Error { .. } => "error",
AtomicToken::Operator { .. } => "operator",
AtomicToken::ShorthandFlag { .. } => "shorthand flag",
AtomicToken::LonghandFlag { .. } => "flag",
AtomicToken::Whitespace { .. } => "whitespace",
AtomicToken::Dot { .. } => "dot",
AtomicToken::Number { .. } => "number",
AtomicToken::FilePath { .. } => "file path",
AtomicToken::Size { .. } => "size",
AtomicToken::String { .. } => "string",
AtomicToken::ItVariable { .. } => "$it",
AtomicToken::Variable { .. } => "variable",
AtomicToken::ExternalCommand { .. } => "external command",
AtomicToken::ExternalWord { .. } => "external word",
AtomicToken::GlobPattern { .. } => "file pattern",
AtomicToken::Word { .. } => "word",
AtomicToken::SquareDelimited { .. } => "array literal",
AtomicToken::ParenDelimited { .. } => "parenthesized expression",
AtomicToken::BraceDelimited { .. } => "block",
AtomicToken::Pipeline { .. } => "pipeline",
}
}
}
pub type SpannedAtomicToken<'tokens> = Spanned<AtomicToken<'tokens>>;
impl<'tokens> SpannedAtomicToken<'tokens> {
@ -95,35 +123,38 @@ impl<'tokens> SpannedAtomicToken<'tokens> {
AtomicToken::Eof { .. } => {
return Err(ParseError::mismatch(
expected,
"eof atomic token".tagged(self.span),
"eof atomic token".spanned(self.span),
))
}
AtomicToken::Error { .. } => {
return Err(ParseError::mismatch(
expected,
"eof atomic token".tagged(self.span),
"eof atomic token".spanned(self.span),
))
}
AtomicToken::Operator { .. } => {
return Err(ParseError::mismatch(expected, "operator".tagged(self.span)))
return Err(ParseError::mismatch(
expected,
"operator".spanned(self.span),
))
}
AtomicToken::ShorthandFlag { .. } => {
return Err(ParseError::mismatch(
expected,
"shorthand flag".tagged(self.span),
"shorthand flag".spanned(self.span),
))
}
AtomicToken::LonghandFlag { .. } => {
return Err(ParseError::mismatch(expected, "flag".tagged(self.span)))
return Err(ParseError::mismatch(expected, "flag".spanned(self.span)))
}
AtomicToken::Whitespace { .. } => {
return Err(ParseError::mismatch(
expected,
"whitespace".tagged(self.span),
"whitespace".spanned(self.span),
))
}
AtomicToken::Dot { .. } => {
return Err(ParseError::mismatch(expected, "dot".tagged(self.span)))
return Err(ParseError::mismatch(expected, "dot".spanned(self.span)))
}
AtomicToken::Number { number } => {
Expression::number(number.to_number(context.source), self.span)
@ -281,6 +312,7 @@ pub struct ExpansionRule {
pub(crate) allow_operator: bool,
pub(crate) allow_eof: bool,
pub(crate) treat_size_as_word: bool,
pub(crate) separate_members: bool,
pub(crate) commit_errors: bool,
pub(crate) whitespace: WhitespaceHandling,
}
@ -293,6 +325,7 @@ impl ExpansionRule {
allow_operator: false,
allow_eof: false,
treat_size_as_word: false,
separate_members: false,
commit_errors: false,
whitespace: WhitespaceHandling::RejectWhitespace,
}
@ -307,6 +340,7 @@ impl ExpansionRule {
allow_external_word: true,
allow_operator: true,
allow_eof: true,
separate_members: false,
treat_size_as_word: false,
commit_errors: true,
whitespace: WhitespaceHandling::AllowWhitespace,
@ -355,6 +389,18 @@ impl ExpansionRule {
self
}
#[allow(unused)]
pub fn separate_members(mut self) -> ExpansionRule {
self.separate_members = true;
self
}
#[allow(unused)]
pub fn no_separate_members(mut self) -> ExpansionRule {
self.separate_members = false;
self
}
#[allow(unused)]
pub fn commit_errors(mut self) -> ExpansionRule {
self.commit_errors = true;
@ -374,9 +420,72 @@ impl ExpansionRule {
}
}
impl<'content> FormatDebug for SpannedAtomicToken<'content> {
fn fmt_debug(&self, f: &mut DebugFormatter, source: &str) -> std::fmt::Result {
match &self.item {
AtomicToken::Eof { .. } => f.say_str("atomic", "eof"),
AtomicToken::Error { .. } => f.say_str("atomic", "error"),
AtomicToken::Number { number } => {
f.say_str("atomic", format!("{}", number.debug(source)))
}
AtomicToken::Size { number, unit } => f.say_str(
"atomic size",
format!("{}{}", number.debug(source), unit.debug(source)),
),
AtomicToken::String { body } => f.say_str("atomic string", body.slice(source)),
AtomicToken::ItVariable { name } => f.say_str("atomic it", name.slice(source)),
AtomicToken::Variable { name } => f.say_str("atomic variable", name.slice(source)),
AtomicToken::ExternalCommand { command } => {
f.say_str("atomic external command", command.slice(source))
}
AtomicToken::ExternalWord { text } => {
f.say_str("atomic external word", text.slice(source))
}
AtomicToken::GlobPattern { pattern } => f.say_str("atomic glob", pattern.slice(source)),
AtomicToken::FilePath { path } => f.say_str("atomic path", path.slice(source)),
AtomicToken::Word { text } => f.say_str("word", text.slice(source)),
AtomicToken::SquareDelimited { .. } => f.say_simple("atomic square"),
AtomicToken::ParenDelimited { .. } => f.say_simple("atomic paren"),
AtomicToken::BraceDelimited { .. } => f.say_simple("atomic brace"),
AtomicToken::Pipeline { .. } => f.say_simple("atomic pipeline"),
AtomicToken::ShorthandFlag { name } => {
f.say_str("atomic shorthand", name.slice(source))
}
AtomicToken::LonghandFlag { name } => f.say_str("atomic longhand", name.slice(source)),
AtomicToken::Dot { .. } => f.say_simple("atomic dot"),
AtomicToken::Operator { text } => f.say_str("atomic operator", text.slice(source)),
AtomicToken::Whitespace { text } => {
f.say_str("atomic whitespace", &format!("{:?}", text.slice(source)))
}
}
}
}
pub fn expand_atom<'me, 'content>(
token_nodes: &'me mut TokensIterator<'content>,
expected: &'static str,
context: &ExpandContext,
rule: ExpansionRule,
) -> Result<SpannedAtomicToken<'content>, ParseError> {
token_nodes.with_expand_tracer(|_, tracer| tracer.start("atom"));
let result = expand_atom_inner(token_nodes, expected, context, rule);
token_nodes.with_expand_tracer(|_, tracer| match &result {
Ok(result) => {
tracer.add_result(Box::new(format!("{}", result.debug(context.source))));
tracer.success();
}
Err(err) => tracer.failed(err),
});
result
}
/// If the caller of expand_atom throws away the returned atomic token returned, it
/// must use a checkpoint to roll it back.
pub fn expand_atom<'me, 'content>(
fn expand_atom_inner<'me, 'content>(
token_nodes: &'me mut TokensIterator<'content>,
expected: &'static str,
context: &ExpandContext,
@ -414,6 +523,30 @@ pub fn expand_atom<'me, 'content>(
},
}
match rule.separate_members {
false => {}
true => {
let mut next = token_nodes.peek_any();
match next.node {
Some(token) if token.is_word() => {
next.commit();
return Ok(AtomicToken::Word { text: token.span() }.spanned(token.span()));
}
Some(token) if token.is_int() => {
next.commit();
return Ok(AtomicToken::Number {
number: RawNumber::Int(token.span()),
}
.spanned(token.span()));
}
_ => {}
}
}
}
// Try to parse the head of the stream as a bare path. A bare path includes
// words as well as `.`s, connected together without whitespace.
match expand_syntax(&BarePathShape, token_nodes, context) {
@ -515,13 +648,7 @@ pub fn expand_atom<'me, 'content>(
// if whitespace is disallowed, return an error
WhitespaceHandling::RejectWhitespace => {
return Err(ParseError::mismatch(
expected,
"whitespace".tagged(Tag {
span: *span,
anchor: None,
}),
))
return Err(ParseError::mismatch(expected, "whitespace".spanned(*span)))
}
},
@ -530,7 +657,8 @@ pub fn expand_atom<'me, 'content>(
peeked.commit();
return Ok(AtomicToken::Error {
error: ShellError::type_error("token", other.tagged_type_name()).spanned(span),
error: ShellError::type_error("token", other.type_name().spanned(span))
.spanned(span),
}
.spanned(span));
}
@ -547,20 +675,14 @@ pub fn expand_atom<'me, 'content>(
RawToken::ExternalCommand(_) if !rule.allow_external_command => {
return Err(ParseError::mismatch(
expected,
token.type_name().tagged(Tag {
span: token_span,
anchor: None,
}),
token.type_name().spanned(token_span),
))
}
// rule.allow_external_word
RawToken::ExternalWord if !rule.allow_external_word => {
return Err(ParseError::mismatch(
expected,
"external word".tagged(Tag {
span: token_span,
anchor: None,
}),
"external word".spanned(token_span),
))
}

View File

@ -35,10 +35,7 @@ impl ExpandExpression for NumberShape {
RawToken::ExternalWord => {
return Err(ParseError::mismatch(
"number",
"syntax error".tagged(Tag {
span: token_span,
anchor: None,
}),
"syntax error".spanned(token_span),
))
}
RawToken::Variable(tag) => hir::Expression::variable(tag, token_span),

View File

@ -29,7 +29,7 @@ impl FallibleColorSyntax for PatternShape {
Ok(())
}
_ => Err(ShellError::type_error("pattern", atom.tagged_type_name())),
_ => Err(ShellError::type_error("pattern", atom.spanned_type_name())),
}
})
}
@ -59,7 +59,10 @@ impl FallibleColorSyntax for PatternShape {
Ok(())
}
_ => Err(ShellError::type_error("pattern", atom.tagged_type_name())),
other => Err(ShellError::type_error(
"pattern",
other.type_name().spanned(atom.span),
)),
}
})
}

View File

@ -50,7 +50,7 @@ impl ExpandSyntax for UnitShape {
let unit = unit_size(span.slice(context.source), *span);
let (_, (number, unit)) = match unit {
Err(_) => return Err(ParseError::mismatch("unit", "word".tagged(Tag::unknown()))),
Err(_) => return Err(ParseError::mismatch("unit", "word".spanned(*span))),
Ok((number, unit)) => (number, unit),
};

View File

@ -1,14 +1,15 @@
use crate::parser::hir::path::PathMember;
use crate::parser::hir::syntax_shape::{
color_fallible_syntax, color_fallible_syntax_with, expand_atom, expand_expr, expand_syntax,
parse_single_node, AnyExpressionShape, AtomicToken, BareShape, ExpandContext, ExpandExpression,
ExpandSyntax, ExpansionRule, FallibleColorSyntax, FlatShape, ParseError, Peeked, SkipSyntax,
StringShape, TestSyntax, WhitespaceShape,
};
use crate::parser::{hir, hir::Expression, hir::TokensIterator, Operator, RawToken};
use crate::parser::{hir, hir::Expression, hir::TokensIterator, Operator, RawNumber, RawToken};
use crate::prelude::*;
use derive_new::new;
use getset::Getters;
use serde::Serialize;
use std::fmt;
use std::str::FromStr;
#[derive(Debug, Copy, Clone)]
pub struct VariablePathShape;
@ -32,7 +33,7 @@ impl ExpandExpression for VariablePathShape {
let head = expand_expr(&VariableShape, token_nodes, context)?;
let start = head.span;
let mut end = start;
let mut tail: Vec<Spanned<String>> = vec![];
let mut tail: Vec<PathMember> = vec![];
loop {
match DotShape.skip(token_nodes, context) {
@ -40,8 +41,8 @@ impl ExpandExpression for VariablePathShape {
Ok(_) => {}
}
let syntax = expand_syntax(&MemberShape, token_nodes, context)?;
let member = syntax.to_spanned_string(context.source);
let member = expand_syntax(&MemberShape, token_nodes, context)?;
let member = member.to_path_member(context.source);
end = member.span;
tail.push(member);
@ -206,8 +207,21 @@ impl FallibleColorSyntax for PathTailShape {
}
}
impl FormatDebug for Spanned<Vec<PathMember>> {
fn fmt_debug(&self, f: &mut DebugFormatter, source: &str) -> fmt::Result {
f.say_list(
"path tail",
&self.item,
|f| write!(f, "["),
|f, item| write!(f, "{}", item.debug(source)),
|f| write!(f, " "),
|f| write!(f, "]"),
)
}
}
impl ExpandSyntax for PathTailShape {
type Output = Spanned<Vec<Spanned<String>>>;
type Output = Spanned<Vec<PathMember>>;
fn name(&self) -> &'static str {
"path continuation"
@ -219,7 +233,7 @@ impl ExpandSyntax for PathTailShape {
context: &ExpandContext,
) -> Result<Self::Output, ParseError> {
let mut end: Option<Span> = None;
let mut tail = vec![];
let mut tail: Vec<PathMember> = vec![];
loop {
match DotShape.skip(token_nodes, context) {
@ -227,23 +241,17 @@ impl ExpandSyntax for PathTailShape {
Ok(_) => {}
}
let syntax = expand_syntax(&MemberShape, token_nodes, context)?;
let member = syntax.to_spanned_string(context.source);
let member = expand_syntax(&MemberShape, token_nodes, context)?;
let member = member.to_path_member(context.source);
end = Some(member.span);
tail.push(member);
}
match end {
None => {
return Err(ParseError::mismatch("path tail", {
let typed_span = token_nodes.typed_span_at_cursor();
Tagged {
tag: typed_span.span.into(),
item: typed_span.item,
}
}))
}
None => Err(ParseError::mismatch(
"path tail",
token_nodes.typed_span_at_cursor(),
)),
Some(end) => Ok(tail.spanned(end)),
}
@ -252,7 +260,7 @@ impl ExpandSyntax for PathTailShape {
#[derive(Debug, Clone)]
pub enum ExpressionContinuation {
DotSuffix(Span, Spanned<String>),
DotSuffix(Span, PathMember),
InfixSuffix(Spanned<Operator>, Expression),
}
@ -303,7 +311,7 @@ impl ExpandSyntax for ExpressionContinuationShape {
// If a `.` was matched, it's a `Path`, and we expect a `Member` next
Ok(dot) => {
let syntax = expand_syntax(&MemberShape, token_nodes, context)?;
let member = syntax.to_spanned_string(context.source);
let member = syntax.to_path_member(context.source);
Ok(ExpressionContinuation::DotSuffix(dot, member))
}
@ -487,7 +495,7 @@ impl FallibleColorSyntax for VariableShape {
shapes.push(FlatShape::ItVariable.spanned(atom.span));
Ok(())
}
_ => Err(ShellError::type_error("variable", atom.tagged_type_name())),
_ => Err(ShellError::type_error("variable", atom.spanned_type_name())),
}
}
}
@ -528,22 +536,44 @@ impl FallibleColorSyntax for VariableShape {
token_nodes.color_shape(FlatShape::ItVariable.spanned(atom.span));
Ok(())
}
_ => Err(ParseError::mismatch("variable", atom.tagged_type_name()).into()),
_ => Err(ParseError::mismatch("variable", atom.type_name().spanned(atom.span)).into()),
}
}
}
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
pub enum Member {
String(/* outer */ Span, /* inner */ Span),
Int(BigInt, Span),
Bare(Span),
}
impl ShellTypeName for Member {
fn type_name(&self) -> &'static str {
match self {
Member::String(_, _) => "string",
Member::Int(_, _) => "integer",
Member::Bare(_) => "word",
}
}
}
impl Member {
pub fn to_path_member(&self, source: &Text) -> PathMember {
match self {
Member::String(outer, inner) => PathMember::string(inner.slice(source), *outer),
Member::Int(int, span) => PathMember::int(int.clone(), *span),
Member::Bare(span) => PathMember::string(span.slice(source), *span),
}
}
}
impl FormatDebug for Member {
fn fmt_debug(&self, f: &mut DebugFormatter, source: &str) -> fmt::Result {
match self {
Member::String(outer, _) => write!(f, "member ({})", outer.slice(source)),
Member::Bare(bare) => write!(f, "member ({})", bare.slice(source)),
Member::String(outer, _) => write!(f, "{}", outer.slice(source)),
Member::Int(_, int) => write!(f, "{}", int.slice(source)),
Member::Bare(bare) => write!(f, "{}", bare.slice(source)),
}
}
}
@ -552,15 +582,17 @@ impl HasSpan for Member {
fn span(&self) -> Span {
match self {
Member::String(outer, ..) => *outer,
Member::Int(_, int) => *int,
Member::Bare(name) => *name,
}
}
}
impl Member {
pub(crate) fn to_expr(&self) -> hir::Expression {
pub fn to_expr(&self) -> hir::Expression {
match self {
Member::String(outer, inner) => hir::Expression::string(*inner, *outer),
Member::Int(number, span) => hir::Expression::number(number.clone(), *span),
Member::Bare(span) => hir::Expression::string(*span, *span),
}
}
@ -568,26 +600,10 @@ impl Member {
pub(crate) fn span(&self) -> Span {
match self {
Member::String(outer, _inner) => *outer,
Member::Int(_, span) => *span,
Member::Bare(span) => *span,
}
}
pub(crate) fn to_spanned_string(&self, source: &str) -> Spanned<String> {
match self {
Member::String(outer, inner) => inner.string(source).spanned(*outer),
Member::Bare(span) => span.spanned_string(source),
}
}
pub(crate) fn tagged_type_name(&self) -> Tagged<&'static str> {
match self {
Member::String(outer, _inner) => "string".tagged(outer),
Member::Bare(span) => "word".tagged(Tag {
span: *span,
anchor: None,
}),
}
}
}
enum ColumnPathState {
@ -603,10 +619,10 @@ impl ColumnPathState {
match self {
ColumnPathState::Initial => ColumnPathState::LeadingDot(dot),
ColumnPathState::LeadingDot(_) => {
ColumnPathState::Error(ParseError::mismatch("column", "dot".tagged(dot)))
ColumnPathState::Error(ParseError::mismatch("column", "dot".spanned(dot)))
}
ColumnPathState::Dot(..) => {
ColumnPathState::Error(ParseError::mismatch("column", "dot".tagged(dot)))
ColumnPathState::Error(ParseError::mismatch("column", "dot".spanned(dot)))
}
ColumnPathState::Member(tag, members) => ColumnPathState::Dot(tag, members, dot),
ColumnPathState::Error(err) => ColumnPathState::Error(err),
@ -626,9 +642,10 @@ impl ColumnPathState {
tags
})
}
ColumnPathState::Member(..) => {
ColumnPathState::Error(ParseError::mismatch("column", member.tagged_type_name()))
}
ColumnPathState::Member(..) => ColumnPathState::Error(ParseError::mismatch(
"column",
member.type_name().spanned(member.span()),
)),
ColumnPathState::Error(err) => ColumnPathState::Error(err),
}
}
@ -637,10 +654,10 @@ impl ColumnPathState {
match self {
ColumnPathState::Initial => Err(next.type_error("column path")),
ColumnPathState::LeadingDot(dot) => {
Err(ParseError::mismatch("column", "dot".tagged(dot)))
Err(ParseError::mismatch("column", "dot".spanned(dot)))
}
ColumnPathState::Dot(_tag, _members, dot) => {
Err(ParseError::mismatch("column", "dot".tagged(dot)))
Err(ParseError::mismatch("column", "dot".spanned(dot)))
}
ColumnPathState::Member(tag, tags) => Ok(tags.tagged(tag)),
ColumnPathState::Error(err) => Err(err),
@ -655,14 +672,14 @@ pub fn expand_column_path<'a, 'b>(
let mut state = ColumnPathState::Initial;
loop {
let member = MemberShape.expand_syntax(token_nodes, context);
let member = expand_syntax(&MemberShape, token_nodes, context);
match member {
Err(_) => break,
Ok(member) => state = state.member(member),
}
let dot = DotShape.expand_syntax(token_nodes, context);
let dot = expand_syntax(&DotShape, token_nodes, context);
match dot {
Err(_) => break,
@ -783,26 +800,8 @@ impl FormatDebug for Tagged<Vec<Member>> {
}
}
#[derive(Debug, Clone, Getters, new)]
pub struct ColumnPath {
#[get = "pub"]
path: Tagged<Vec<Member>>,
}
impl HasSpan for ColumnPath {
fn span(&self) -> Span {
self.path.tag.span
}
}
impl FormatDebug for ColumnPath {
fn fmt_debug(&self, f: &mut DebugFormatter, source: &str) -> fmt::Result {
f.say("column path", self.path.item.debug(source))
}
}
impl ExpandSyntax for ColumnPathShape {
type Output = ColumnPath;
type Output = Tagged<Vec<Member>>;
fn name(&self) -> &'static str {
"column path"
@ -813,7 +812,7 @@ impl ExpandSyntax for ColumnPathShape {
token_nodes: &'b mut TokensIterator<'a>,
context: &ExpandContext,
) -> Result<Self::Output, ParseError> {
Ok(ColumnPath::new(expand_column_path(token_nodes, context)?))
Ok(expand_column_path(token_nodes, context)?)
}
}
@ -888,6 +887,55 @@ impl FallibleColorSyntax for MemberShape {
}
}
#[derive(Debug, Copy, Clone)]
struct IntMemberShape;
impl ExpandSyntax for IntMemberShape {
type Output = Member;
fn name(&self) -> &'static str {
"integer member"
}
fn expand_syntax<'a, 'b>(
&self,
token_nodes: &'b mut TokensIterator<'a>,
context: &ExpandContext,
) -> Result<Self::Output, ParseError> {
token_nodes.atomic_parse(|token_nodes| {
let next = expand_atom(
token_nodes,
"integer member",
context,
ExpansionRule::new().separate_members(),
)?;
match next.item {
AtomicToken::Number {
number: RawNumber::Int(int),
} => Ok(Member::Int(
BigInt::from_str(int.slice(context.source)).unwrap(),
int,
)),
AtomicToken::Word { text } => {
let int = BigInt::from_str(text.slice(context.source));
match int {
Ok(int) => return Ok(Member::Int(int, text)),
Err(_) => Err(ParseError::mismatch("integer member", "word".spanned(text))),
}
}
other => Err(ParseError::mismatch(
"integer member",
other.type_name().spanned(next.span),
)),
}
})
}
}
impl ExpandSyntax for MemberShape {
type Output = Member;
@ -900,6 +948,10 @@ impl ExpandSyntax for MemberShape {
token_nodes: &mut TokensIterator<'_>,
context: &ExpandContext,
) -> Result<Member, ParseError> {
if let Ok(int) = expand_syntax(&IntMemberShape, token_nodes, context) {
return Ok(int);
}
let bare = BareShape.test(token_nodes, context);
if let Some(peeked) = bare {
let node = peeked.not_eof("column")?.commit();
@ -956,7 +1008,7 @@ impl FallibleColorSyntax for ColorableDotShape {
Ok(())
}
other => Err(ShellError::type_error("dot", other.tagged_type_name())),
other => Err(ShellError::type_error("dot", other.spanned_type_name())),
}
}
}
@ -985,7 +1037,10 @@ impl FallibleColorSyntax for ColorableDotShape {
Ok(())
}
other => Err(ShellError::type_error("dot", other.tagged_type_name())),
other => Err(ShellError::type_error(
"dot",
other.type_name().spanned(other.span()),
)),
}
}
}
@ -1020,7 +1075,7 @@ impl ExpandSyntax for DotShape {
_ => {
return Err(ParseError::mismatch(
"dot",
token.type_name().tagged(token_span),
token.type_name().spanned(token_span),
))
}
})
@ -1108,7 +1163,7 @@ impl FallibleColorSyntax for InfixShape {
// Otherwise, it's not a match
_ => Err(ParseError::mismatch(
"infix operator",
token.type_name().tagged(token_span),
token.type_name().spanned(token_span),
)),
}
},

View File

@ -150,7 +150,7 @@ impl<'content, 'me> PeekedNode<'content, 'me> {
pub fn peek_error(node: &Option<&TokenNode>, eof_span: Span, expected: &'static str) -> ParseError {
match node {
None => ParseError::unexpected_eof(expected, eof_span),
Some(node) => ParseError::mismatch(expected, node.tagged_type_name()),
Some(node) => ParseError::mismatch(expected, node.type_name().spanned(node.span())),
}
}
@ -343,7 +343,7 @@ impl<'content> TokensIterator<'content> {
pub fn expand_frame<T>(
&mut self,
desc: &'static str,
block: impl FnOnce(&mut TokensIterator) -> Result<T, ParseError>,
block: impl FnOnce(&mut TokensIterator<'content>) -> Result<T, ParseError>,
) -> Result<T, ParseError>
where
T: std::fmt::Debug + FormatDebug + Clone + HasFallibleSpan + 'static,
@ -458,6 +458,34 @@ impl<'content> TokensIterator<'content> {
return Ok(value);
}
/// Use a checkpoint when you need to peek more than one token ahead, but can't be sure
/// that you'll succeed.
pub fn atomic_parse<'me, T>(
&'me mut self,
block: impl FnOnce(&mut TokensIterator<'content>) -> Result<T, ParseError>,
) -> Result<T, ParseError> {
let state = &mut self.state;
let index = state.index;
#[cfg(coloring_in_tokens)]
let shape_start = state.shapes.len();
let seen = state.seen.clone();
let checkpoint = Checkpoint {
iterator: self,
index,
seen,
committed: false,
#[cfg(coloring_in_tokens)]
shape_start,
};
let value = block(checkpoint.iterator)?;
checkpoint.commit();
return Ok(value);
}
#[cfg(coloring_in_tokens)]
/// Use a checkpoint when you need to peek more than one token ahead, but can't be sure
/// that you'll succeed.

View File

@ -164,6 +164,12 @@ impl Into<Number> for BigDecimal {
}
}
impl Into<Number> for BigInt {
fn into(self) -> Number {
Number::Int(self)
}
}
#[tracable_parser]
pub fn number(input: NomSpan) -> IResult<NomSpan, TokenNode> {
let (input, number) = raw_number(input)?;

View File

@ -147,7 +147,10 @@ impl TokenNode {
item: RawToken::Variable(inner_span),
span: outer_span,
}) => Ok((*outer_span, *inner_span)),
_ => Err(ShellError::type_error("variable", self.tagged_type_name())),
_ => Err(ShellError::type_error(
"variable",
self.type_name().spanned(self.span()),
)),
}
}
@ -201,6 +204,26 @@ impl TokenNode {
}
}
pub fn is_word(&self) -> bool {
match self {
TokenNode::Token(Spanned {
item: RawToken::Bare,
..
}) => true,
_ => false,
}
}
pub fn is_int(&self) -> bool {
match self {
TokenNode::Token(Spanned {
item: RawToken::Number(RawNumber::Int(_)),
..
}) => true,
_ => false,
}
}
pub fn is_dot(&self) -> bool {
match self {
TokenNode::Token(Spanned {
@ -250,7 +273,10 @@ impl TokenNode {
pub fn as_pipeline(&self) -> Result<Pipeline, ParseError> {
match self {
TokenNode::Pipeline(Spanned { item, .. }) => Ok(item.clone()),
other => Err(ParseError::mismatch("pipeline", other.tagged_type_name())),
other => Err(ParseError::mismatch(
"pipeline",
other.type_name().spanned(other.span()),
)),
}
}

View File

@ -10,7 +10,7 @@ use crate::parser::{
Flag,
};
use crate::traits::ToDebug;
use crate::{Span, Spanned, Tag, Text};
use crate::{Span, Spanned, SpannedItem, Text};
use log::trace;
pub fn parse_command_tail(
@ -39,9 +39,8 @@ pub fn parse_command_tail(
if tail.at_end() {
return Err(ParseError::argument_error(
config.name.clone(),
config.name.clone().spanned(flag.span),
ArgumentError::MissingValueForName(name.to_string()),
flag.span,
));
}
@ -60,9 +59,8 @@ pub fn parse_command_tail(
if tail.at_end() {
return Err(ParseError::argument_error(
config.name.clone(),
config.name.clone().spanned(flag.span),
ArgumentError::MissingValueForName(name.to_string()),
flag.span,
));
}
@ -96,12 +94,8 @@ pub fn parse_command_tail(
PositionalType::Mandatory(..) => {
if tail.at_end_possible_ws() {
return Err(ParseError::argument_error(
config.name.clone(),
config.name.clone().spanned(command_span),
ArgumentError::MissingMandatoryPositional(arg.0.name().to_string()),
Tag {
span: command_span,
anchor: None,
},
));
}
}
@ -593,9 +587,8 @@ fn extract_mandatory(
match flag {
None => Err(ParseError::argument_error(
config.name.clone(),
config.name.clone().spanned(span),
ArgumentError::MissingMandatoryFlag(name.to_string()),
span,
)),
Some((pos, flag)) => {

View File

@ -7,7 +7,6 @@ use derive_new::new;
use indexmap::IndexMap;
use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Debug, Serialize, Deserialize, Clone)]
pub enum NamedType {
@ -178,63 +177,7 @@ impl EvaluatedArgs {
}
}
#[derive(new)]
pub struct DebugEvaluatedPositional<'a> {
positional: &'a Option<Vec<Tagged<Value>>>,
}
impl fmt::Debug for DebugEvaluatedPositional<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match &self.positional {
None => write!(f, "None"),
Some(positional) => f
.debug_list()
.entries(positional.iter().map(|p| p.debug()))
.finish(),
}
}
}
#[derive(new)]
pub struct DebugEvaluatedNamed<'a> {
named: &'a Option<IndexMap<String, Tagged<Value>>>,
}
impl fmt::Debug for DebugEvaluatedNamed<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match &self.named {
None => write!(f, "None"),
Some(named) => f
.debug_map()
.entries(named.iter().map(|(k, v)| (k, v.debug())))
.finish(),
}
}
}
pub struct DebugEvaluatedArgs<'a> {
args: &'a EvaluatedArgs,
}
impl fmt::Debug for DebugEvaluatedArgs<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut s = f.debug_struct("Args");
s.field(
"positional",
&DebugEvaluatedPositional::new(&self.args.positional),
);
s.field("named", &DebugEvaluatedNamed::new(&self.args.named));
s.finish()
}
}
impl EvaluatedArgs {
pub fn debug(&self) -> DebugEvaluatedArgs<'_> {
DebugEvaluatedArgs { args: self }
}
pub fn nth(&self, pos: usize) -> Option<&Tagged<Value>> {
match &self.positional {
None => None,

View File

@ -143,8 +143,8 @@ fn send_response<T: Serialize>(result: T) {
let response_raw = serde_json::to_string(&response);
match response_raw {
Ok(response) => println!("{}", response),
Err(err) => println!("{}", err),
Ok(response) => outln!("{}", response),
Err(err) => outln!("{}", err),
}
}
#[derive(Debug, Serialize, Deserialize)]

View File

@ -1,6 +1,7 @@
use crossterm::{cursor, terminal, Attribute, RawScreen};
use nu::{
serve_plugin, AnchorLocation, CallInfo, Plugin, Primitive, ShellError, Signature, Tagged, Value,
outln, serve_plugin, AnchorLocation, CallInfo, Plugin, Primitive, ShellError, Signature,
Tagged, Value,
};
use pretty_hex::*;
@ -47,7 +48,7 @@ fn view_binary(
}
#[cfg(not(feature = "rawkey"))]
{
println!("Interactive binary viewing currently requires the 'rawkey' feature");
outln!("Interactive binary viewing currently requires the 'rawkey' feature");
return Ok(());
}
}
@ -117,7 +118,7 @@ impl RenderContext {
);
}
}
println!("{}", Attribute::Reset);
outln!("{}", Attribute::Reset);
Ok(())
}
fn render_to_screen_hires(&mut self) -> Result<(), Box<dyn std::error::Error>> {
@ -174,7 +175,7 @@ impl RenderContext {
_ => {}
}
}
println!("{}", Attribute::Reset);
outln!("{}", Attribute::Reset);
Ok(())
}
pub fn flush(&mut self) -> Result<(), Box<dyn std::error::Error>> {
@ -264,7 +265,7 @@ pub fn view_contents(
if raw_image_buffer.is_none() {
//Not yet supported
println!("{:?}", buffer.hex_dump());
outln!("{:?}", buffer.hex_dump());
return Ok(());
}
let raw_image_buffer = raw_image_buffer.unwrap();
@ -322,7 +323,7 @@ pub fn view_contents(
}
_ => {
//Not yet supported
println!("{:?}", buffer.hex_dump());
outln!("{:?}", buffer.hex_dump());
return Ok(());
}
}

View File

@ -1,12 +1,10 @@
use nu::{
serve_plugin, CallInfo, Plugin, ReturnSuccess, ReturnValue, ShellError, Signature, SyntaxShape,
Tagged, Value,
serve_plugin, CallInfo, ColumnPath, Plugin, Primitive, ReturnSuccess, ReturnValue, ShellError,
ShellTypeName, Signature, SpannedItem, SyntaxShape, Tagged, TaggedItem, Value,
};
pub type ColumnPath = Tagged<Vec<Tagged<Value>>>;
struct Edit {
field: Option<ColumnPath>,
field: Option<Tagged<ColumnPath>>,
value: Option<Value>,
}
impl Edit {
@ -21,7 +19,7 @@ impl Edit {
let value_tag = value.tag();
match (value.item, self.value.clone()) {
(obj @ Value::Row(_), Some(v)) => match &self.field {
Some(f) => match obj.replace_data_at_column_path(value_tag, &f, v) {
Some(f) => match obj.tagged(value_tag).replace_data_at_column_path(&f, v) {
Some(v) => return Ok(v),
None => {
return Err(ShellError::labeled_error(
@ -65,12 +63,17 @@ impl Plugin for Edit {
if let Some(args) = call_info.args.positional {
match &args[0] {
table @ Tagged {
item: Value::Table(_),
item: Value::Primitive(Primitive::ColumnPath(_)),
..
} => {
self.field = Some(table.as_column_path()?);
}
value => return Err(ShellError::type_error("table", value.tagged_type_name())),
value => {
return Err(ShellError::type_error(
"table",
value.type_name().spanned(value.span()),
))
}
}
match &args[1] {
Tagged { item: v, .. } => {

View File

@ -2,8 +2,8 @@
extern crate indexmap;
use nu::{
serve_plugin, CallInfo, Plugin, Primitive, ReturnSuccess, ReturnValue, ShellError, Signature,
SyntaxShape, Tag, Tagged, TaggedItem, Value,
serve_plugin, CallInfo, Plugin, Primitive, ReturnSuccess, ReturnValue, ShellError,
ShellTypeName, Signature, SpannedItem, SyntaxShape, Tag, Tagged, TaggedItem, Value,
};
struct Embed {
@ -42,7 +42,12 @@ impl Plugin for Embed {
self.field = Some(s.clone());
self.values = Vec::new();
}
value => return Err(ShellError::type_error("string", value.tagged_type_name())),
value => {
return Err(ShellError::type_error(
"string",
value.type_name().spanned(value.span()),
))
}
}
}

View File

@ -1,6 +1,7 @@
use nu::{
did_you_mean, serve_plugin, tag_for_tagged_list, CallInfo, Plugin, Primitive, ReturnSuccess,
ReturnValue, ShellError, Signature, SyntaxShape, Tagged, TaggedItem, Value,
did_you_mean, serve_plugin, span_for_spanned_list, CallInfo, ColumnPath, Plugin, Primitive,
ReturnSuccess, ReturnValue, ShellError, ShellTypeName, Signature, SpannedItem, SyntaxShape,
Tagged, TaggedItem, Value,
};
enum Action {
@ -14,10 +15,8 @@ pub enum SemVerAction {
Patch,
}
pub type ColumnPath = Tagged<Vec<Tagged<Value>>>;
struct Inc {
field: Option<ColumnPath>,
field: Option<Tagged<ColumnPath>>,
error: Option<String>,
action: Option<Action>,
}
@ -89,7 +88,7 @@ impl Inc {
} else {
return Err(ShellError::type_error(
"incrementable value",
value.tagged_type_name(),
value.type_name().spanned(value.span()),
));
}
}
@ -98,48 +97,32 @@ impl Inc {
Some(ref f) => {
let fields = f.clone();
let replace_for = value.item.get_data_by_column_path(
value.tag(),
f,
Box::new(move |(obj_source, column_path_tried)| {
let replace_for = value.get_data_by_column_path(
&f,
Box::new(move |(obj_source, column_path_tried, _)| {
match did_you_mean(&obj_source, &column_path_tried) {
Some(suggestions) => {
return ShellError::labeled_error(
"Unknown column",
format!("did you mean '{}'?", suggestions[0].1),
tag_for_tagged_list(fields.iter().map(|p| p.tag())),
span_for_spanned_list(fields.iter().map(|p| p.span)),
)
}
None => {
return ShellError::labeled_error(
"Unknown column",
"row does not contain this column",
tag_for_tagged_list(fields.iter().map(|p| p.tag())),
span_for_spanned_list(fields.iter().map(|p| p.span)),
)
}
}
}),
);
let replacement = match replace_for {
Ok(got) => match got {
Some(result) => self.inc(result.map(|x| x.clone()))?,
None => {
return Err(ShellError::labeled_error(
"inc could not find field to replace",
"column name",
value.tag(),
))
}
},
Err(reason) => return Err(reason),
};
let got = replace_for?;
let replacement = self.inc(got.map(|x| x.clone()))?;
match value.item.replace_data_at_column_path(
value.tag(),
&f,
replacement.item.clone(),
) {
match value.replace_data_at_column_path(&f, replacement.item.clone()) {
Some(v) => return Ok(v),
None => {
return Err(ShellError::labeled_error(
@ -156,7 +139,7 @@ impl Inc {
},
_ => Err(ShellError::type_error(
"incrementable value",
value.tagged_type_name(),
value.type_name().spanned(value.span()),
)),
}
}
@ -188,12 +171,17 @@ impl Plugin for Inc {
for arg in args {
match arg {
table @ Tagged {
item: Value::Table(_),
item: Value::Primitive(Primitive::ColumnPath(_)),
..
} => {
self.field = Some(table.as_column_path()?);
}
value => return Err(ShellError::type_error("table", value.tagged_type_name())),
value => {
return Err(ShellError::type_error(
"table",
value.type_name().spanned(value.span()),
))
}
}
}
}
@ -229,8 +217,8 @@ mod tests {
use super::{Inc, SemVerAction};
use indexmap::IndexMap;
use nu::{
CallInfo, EvaluatedArgs, Plugin, Primitive, ReturnSuccess, Tag, Tagged, TaggedDictBuilder,
TaggedItem, Value,
CallInfo, EvaluatedArgs, PathMember, Plugin, RawPathMember, ReturnSuccess, SpannedItem,
Tag, Tagged, TaggedDictBuilder, TaggedItem, Value,
};
struct CallStub {
@ -255,13 +243,13 @@ mod tests {
}
fn with_parameter(&mut self, name: &str) -> &mut Self {
let fields: Vec<Tagged<Value>> = name
let fields: Vec<PathMember> = name
.split(".")
.map(|s| Value::string(s.to_string()).tagged(Tag::unknown()))
.map(|s| RawPathMember::String(s.to_string()).spanned_unknown())
.collect();
self.positionals
.push(Value::Table(fields).tagged(Tag::unknown()));
.push(Value::column_path(fields).tagged_unknown());
self
}
@ -344,14 +332,13 @@ mod tests {
.is_ok());
assert_eq!(
plugin.field.map(|f| f
.iter()
.map(|f| match &f.item {
Value::Primitive(Primitive::String(s)) => s.clone(),
_ => panic!(""),
})
.collect()),
Some(vec!["package".to_string(), "version".to_string()])
plugin
.field
.map(|f| f.iter().map(|f| f.item.clone()).collect()),
Some(vec![
RawPathMember::String("package".to_string()),
RawPathMember::String("version".to_string())
])
);
}

View File

@ -1,14 +1,11 @@
use itertools::Itertools;
use nu::{
serve_plugin, CallInfo, Plugin, ReturnSuccess, ReturnValue, ShellError, Signature, SyntaxShape,
Tagged, TaggedItem, Value,
serve_plugin, CallInfo, ColumnPath, Plugin, Primitive, ReturnSuccess, ReturnValue, ShellError,
ShellTypeName, Signature, SpannedItem, SyntaxShape, Tagged, Value,
};
pub type ColumnPath = Vec<Tagged<Value>>;
struct Insert {
field: Option<ColumnPath>,
value: Option<Value>,
value: Option<Tagged<Value>>,
}
impl Insert {
fn new() -> Insert {
@ -20,38 +17,19 @@ impl Insert {
fn insert(&self, value: Tagged<Value>) -> Result<Tagged<Value>, ShellError> {
let value_tag = value.tag();
match (value.item, self.value.clone()) {
(obj @ Value::Row(_), Some(v)) => match &self.field {
Some(f) => match obj.insert_data_at_column_path(value_tag.clone(), f, v) {
Some(v) => return Ok(v),
None => {
return Err(ShellError::labeled_error(
format!(
"add could not find place to insert field {:?} {}",
obj,
f.iter()
.map(|i| {
match &i.item {
Value::Primitive(primitive) => primitive.format(None),
_ => String::from(""),
}
})
.join(".")
),
"column name",
&value_tag,
))
}
match (&value, &self.value, &self.field) {
(
obj @ Tagged {
item: Value::Row(_),
..
},
None => Err(ShellError::labeled_error(
"add needs a column name when adding a value to a table",
"column name",
value_tag,
)),
},
(value, _) => Err(ShellError::type_error(
Some(v),
Some(field),
) => obj.clone().insert_data_at_column_path(field, v.clone()),
(value, ..) => Err(ShellError::type_error(
"row",
value.type_name().tagged(value_tag),
value.type_name().spanned(value_tag),
)),
}
}
@ -78,16 +56,21 @@ impl Plugin for Insert {
if let Some(args) = call_info.args.positional {
match &args[0] {
table @ Tagged {
item: Value::Table(_),
item: Value::Primitive(Primitive::ColumnPath(_)),
..
} => {
self.field = Some(table.as_column_path()?.item);
}
value => return Err(ShellError::type_error("table", value.tagged_type_name())),
value => {
return Err(ShellError::type_error(
"table",
value.type_name().spanned(value.span()),
))
}
}
match &args[1] {
Tagged { item: v, .. } => {
v @ Tagged { .. } => {
self.value = Some(v.clone());
}
}

View File

@ -1,6 +1,7 @@
use nu::{
did_you_mean, serve_plugin, tag_for_tagged_list, CallInfo, Plugin, Primitive, ReturnSuccess,
ReturnValue, ShellError, Signature, SyntaxShape, Tagged, TaggedItem, Value,
did_you_mean, serve_plugin, span_for_spanned_list, CallInfo, ColumnPath, Plugin, Primitive,
ReturnSuccess, ReturnValue, ShellError, ShellTypeName, Signature, SyntaxShape, Tagged,
TaggedItem, Value,
};
use std::cmp;
@ -12,10 +13,8 @@ enum Action {
Substring(usize, usize),
}
pub type ColumnPath = Tagged<Vec<Tagged<Value>>>;
struct Str {
field: Option<ColumnPath>,
field: Option<Tagged<ColumnPath>>,
params: Option<Vec<String>>,
error: Option<String>,
action: Option<Action>,
@ -62,7 +61,7 @@ impl Str {
Ok(applied)
}
fn for_field(&mut self, column_path: ColumnPath) {
fn for_field(&mut self, column_path: Tagged<ColumnPath>) {
self.field = Some(column_path);
}
@ -130,55 +129,33 @@ impl Str {
Some(ref f) => {
let fields = f.clone();
let replace_for = value.item.get_data_by_column_path(
value.tag(),
f,
Box::new(move |(obj_source, column_path_tried)| {
let replace_for =
value.get_data_by_column_path(
&f,
Box::new(move |(obj_source, column_path_tried, error)| {
match did_you_mean(&obj_source, &column_path_tried) {
Some(suggestions) => {
return ShellError::labeled_error(
"Unknown column",
format!("did you mean '{}'?", suggestions[0].1),
tag_for_tagged_list(fields.iter().map(|p| p.tag())),
)
}
None => {
return ShellError::labeled_error(
"Unknown column",
"row does not contain this column",
tag_for_tagged_list(fields.iter().map(|p| p.tag())),
span_for_spanned_list(fields.iter().map(|p| p.span)),
)
}
None => return error,
}
}),
);
let replacement = match replace_for {
Ok(got) => match got {
Some(result) => self.strutils(result.map(|x| x.clone()))?,
None => {
return Err(ShellError::labeled_error(
"inc could not find field to replace",
"column name",
value.tag(),
))
}
},
Err(reason) => return Err(reason),
};
let got = replace_for?;
let replacement = self.strutils(got.map(|x| x.clone()))?;
match value.item.replace_data_at_column_path(
value.tag(),
f,
replacement.item.clone(),
) {
match value.replace_data_at_column_path(&f, replacement.item.clone()) {
Some(v) => return Ok(v),
None => {
return Err(ShellError::type_error(
None => Err(ShellError::labeled_error(
"str could not find field to replace",
"column name",
value.tagged_type_name(),
))
}
value.tag(),
)),
}
}
None => Err(ShellError::untagged_runtime_error(format!(
@ -189,7 +166,7 @@ impl Str {
},
_ => Err(ShellError::labeled_error(
"Unrecognized type in stream",
value.type_name(),
value.item.type_name(),
value.tag,
)),
}
@ -245,27 +222,9 @@ impl Plugin for Str {
}
if let Some(possible_field) = args.nth(0) {
match possible_field {
string @ Tagged {
item: Value::Primitive(Primitive::String(_)),
..
} => {
self.for_field(string.as_column_path()?);
}
table @ Tagged {
item: Value::Table(_),
..
} => {
self.field = Some(table.as_column_path()?);
}
_ => {
return Err(ShellError::labeled_error(
"Unrecognized type in params",
possible_field.type_name(),
&possible_field.tag,
))
}
}
let possible_field = possible_field.as_column_path()?;
self.for_field(possible_field);
}
for param in args.positional_iter() {
match param {
@ -303,8 +262,8 @@ mod tests {
use super::{Action, Str};
use indexmap::IndexMap;
use nu::{
CallInfo, EvaluatedArgs, Plugin, Primitive, ReturnSuccess, Tag, Tagged, TaggedDictBuilder,
TaggedItem, Value,
CallInfo, EvaluatedArgs, Plugin, Primitive, RawPathMember, ReturnSuccess, Tag, Tagged,
TaggedDictBuilder, TaggedItem, Value,
};
use num_bigint::BigInt;
@ -419,14 +378,13 @@ mod tests {
.is_ok());
assert_eq!(
plugin.field.map(|f| f
.iter()
.map(|f| match &f.item {
Value::Primitive(Primitive::String(s)) => s.clone(),
_ => panic!(""),
})
.collect()),
Some(vec!["package".to_string(), "description".to_string()])
plugin
.field
.map(|f| f.iter().cloned().map(|f| f.item).collect()),
Some(vec![
RawPathMember::String("package".to_string()),
RawPathMember::String("description".to_string())
])
)
}

View File

@ -1,7 +1,8 @@
use crossterm::{cursor, terminal, RawScreen};
use crossterm::{InputEvent, KeyEvent};
use nu::{
serve_plugin, AnchorLocation, CallInfo, Plugin, Primitive, ShellError, Signature, Tagged, Value,
outln, serve_plugin, AnchorLocation, CallInfo, Plugin, Primitive, ShellError, Signature,
Tagged, Value,
};
use syntect::easy::HighlightLines;
@ -15,6 +16,7 @@ enum DrawCommand {
DrawString(Style, String),
NextLine,
}
struct TextView;
impl TextView {
@ -202,7 +204,7 @@ fn scroll_view_lines_if_needed(draw_commands: Vec<DrawCommand>, use_color_buffer
}
}
println!("");
outln!("");
}
fn scroll_view(s: &str) {

View File

@ -23,12 +23,13 @@ macro_rules! stream {
#[macro_export]
macro_rules! trace_stream {
(target: $target:tt, $desc:tt = $expr:expr) => {{
(target: $target:tt, source: $source:expr, $desc:tt = $expr:expr) => {{
if log::log_enabled!(target: $target, log::Level::Trace) {
use futures::stream::StreamExt;
let source = $source.clone();
let objects = $expr.values.inspect(|o| {
trace!(target: $target, "{} = {:#?}", $desc, o.debug());
let objects = $expr.values.inspect(move |o| {
trace!(target: $target, "{} = {}", $desc, o.debug(&source));
});
$crate::stream::InputStream::from_stream(objects.boxed())
@ -57,6 +58,38 @@ macro_rules! trace_out_stream {
}};
}
// These macros exist to differentiate between intentional writing to stdout
// and stray printlns left by accident
#[macro_export]
macro_rules! outln {
($($tokens:tt)*) => { println!($($tokens)*) }
}
#[macro_export]
macro_rules! errln {
($($tokens:tt)*) => { eprintln!($($tokens)*) }
}
#[macro_export]
macro_rules! dict {
($( $key:expr => $value:expr ),*) => {
$crate::data::dict::TaggedDictBuilder::build(Tag::unknown(), |d| {
$(
d.insert($key, $value);
)*
})
};
([tag] => $tag:expr, $( $key:expr => $value:expr ),*) => {
$crate::data::dict::TaggedDictBuilder::build($tag, |d| {
$(
d.insert($key, $value);
)*
})
}
}
pub(crate) use crate::cli::MaybeOwned;
pub(crate) use crate::commands::command::{
CallInfo, CommandAction, CommandArgs, ReturnSuccess, ReturnValue, RunnableContext,
@ -67,7 +100,7 @@ pub(crate) use crate::context::CommandRegistry;
pub(crate) use crate::context::{AnchorLocation, Context};
pub(crate) use crate::data::base as value;
pub(crate) use crate::data::meta::{
tag_for_tagged_list, HasFallibleSpan, HasSpan, Span, Spanned, SpannedItem, Tag, Tagged,
span_for_spanned_list, HasFallibleSpan, HasSpan, Span, Spanned, SpannedItem, Tag, Tagged,
TaggedItem,
};
pub(crate) use crate::data::types::ExtractType;
@ -83,7 +116,10 @@ pub(crate) use crate::shell::help_shell::HelpShell;
pub(crate) use crate::shell::shell_manager::ShellManager;
pub(crate) use crate::shell::value_shell::ValueShell;
pub(crate) use crate::stream::{InputStream, OutputStream};
pub(crate) use crate::traits::{DebugFormatter, FormatDebug, HasTag, ToDebug};
pub(crate) use crate::traits::{
DebugDoc, DebugDocBuilder, DebugFormatter, FormatDebug, HasTag, PrettyDebug, PrettyType,
ShellTypeName, SpannedTypeName, ToDebug,
};
pub(crate) use crate::Text;
pub(crate) use async_stream::stream as async_stream;
pub(crate) use bigdecimal::BigDecimal;

View File

@ -27,7 +27,11 @@ impl HelpShell {
spec.insert("name", cmd);
spec.insert(
"description",
value.get_data_by_key("usage").unwrap().as_string().unwrap(),
value
.get_data_by_key("usage".spanned_unknown())
.unwrap()
.as_string()
.unwrap(),
);
spec.insert_tagged("details", value);
@ -70,7 +74,7 @@ impl HelpShell {
for p in full_path.iter() {
match p {
x if x == sep => {}
step => match viewed.get_data_by_key(step.to_str().unwrap()) {
step => match viewed.get_data_by_key(step.to_str().unwrap().spanned_unknown()) {
Some(v) => {
viewed = v.clone();
}

View File

@ -106,10 +106,10 @@ impl Highlighter for Helper {
trace!(target: "nu::color_syntax", "{:#?}", tokens.color_tracer());
if log_enabled!(target: "nu::color_syntax", log::Level::Debug) {
println!("");
outln!("");
ptree::print_tree(&tokens.color_tracer().clone().print(Text::from(line)))
.unwrap();
println!("");
outln!("");
}
for shape in shapes {

View File

@ -40,7 +40,7 @@ impl ValueShell {
for p in full_path.iter() {
match p {
x if x == sep => {}
step => match viewed.get_data_by_key(step.to_str().unwrap()) {
step => match viewed.get_data_by_key(step.to_str().unwrap().spanned_unknown()) {
Some(v) => {
viewed = v.clone();
}

View File

@ -1,6 +1,36 @@
use crate::prelude::*;
use derive_new::new;
use getset::Getters;
use pretty::{BoxAllocator, DocAllocator};
use std::fmt::{self, Write};
use std::hash::Hash;
use termcolor::{Color, ColorSpec};
pub trait ShellTypeName {
fn type_name(&self) -> &'static str;
}
impl<T: ShellTypeName> ShellTypeName for &T {
fn type_name(&self) -> &'static str {
(*self).type_name()
}
}
pub trait SpannedTypeName {
fn spanned_type_name(&self) -> Spanned<&'static str>;
}
impl<T: ShellTypeName> SpannedTypeName for Spanned<T> {
fn spanned_type_name(&self) -> Spanned<&'static str> {
self.item.type_name().spanned(self.span)
}
}
impl<T: ShellTypeName> SpannedTypeName for Tagged<T> {
fn spanned_type_name(&self) -> Spanned<&'static str> {
self.item.type_name().spanned(self.tag.span)
}
}
pub struct Debuggable<'a, T: FormatDebug> {
inner: &'a T,
@ -13,7 +43,7 @@ impl FormatDebug for str {
}
}
impl<T: ToDebug> fmt::Display for Debuggable<'_, T> {
impl<T: ToDebug> fmt::Debug for Debuggable<'_, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.inner.fmt_debug(
&mut DebugFormatter::new(
@ -26,11 +56,24 @@ impl<T: ToDebug> fmt::Display for Debuggable<'_, T> {
}
}
impl<T: ToDebug> fmt::Display for Debuggable<'_, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.inner.fmt_display(
&mut DebugFormatter::new(
f,
ansi_term::Color::White.bold(),
ansi_term::Color::Black.bold(),
),
self.source,
)
}
}
pub trait HasTag {
fn tag(&self) -> Tag;
}
#[derive(new)]
#[derive(Getters, new)]
pub struct DebugFormatter<'me, 'args> {
formatter: &'me mut std::fmt::Formatter<'args>,
style: ansi_term::Style,
@ -38,6 +81,10 @@ pub struct DebugFormatter<'me, 'args> {
}
impl<'me, 'args> DebugFormatter<'me, 'args> {
pub fn say_simple(&mut self, kind: &str) -> std::fmt::Result {
write!(self, "{}", self.style.paint(kind))
}
pub fn say<'debuggable>(
&mut self,
kind: &str,
@ -72,6 +119,40 @@ impl<'me, 'args> DebugFormatter<'me, 'args> {
block(self)
}
pub fn say_list<T, U: IntoIterator<Item = T>>(
&mut self,
kind: &str,
list: U,
open: impl Fn(&mut Self) -> std::fmt::Result,
mut block: impl FnMut(&mut Self, &T) -> std::fmt::Result,
interleave: impl Fn(&mut Self) -> std::fmt::Result,
close: impl Fn(&mut Self) -> std::fmt::Result,
) -> std::fmt::Result {
write!(self, "{}", self.style.paint(kind))?;
write!(self, "{}", self.default_style.paint(" "))?;
open(self)?;
write!(self, " ")?;
let mut list = list.into_iter();
let first = match list.next() {
None => return Ok(()),
Some(first) => first,
};
block(self, &first)?;
for item in list {
interleave(self)?;
block(self, &item)?;
}
write!(self, " ")?;
close(self)?;
Ok(())
}
pub fn say_dict<'debuggable>(
&mut self,
kind: &str,
@ -111,8 +192,342 @@ impl<'a, 'b> std::fmt::Write for DebugFormatter<'a, 'b> {
}
}
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, Hash)]
pub enum ShellStyle {
Delimiter,
Key,
Value,
Equals,
Kind,
Keyword,
Primitive,
Opaque,
Description,
Error,
}
impl From<ShellAnnotation> for ColorSpec {
fn from(ann: ShellAnnotation) -> ColorSpec {
match ann.style {
ShellStyle::Delimiter => ColorSpec::new()
.set_fg(Some(Color::White))
.set_intense(false)
.clone(),
ShellStyle::Key => ColorSpec::new()
.set_fg(Some(Color::Black))
.set_intense(true)
.clone(),
ShellStyle::Value => ColorSpec::new()
.set_fg(Some(Color::White))
.set_intense(true)
.clone(),
ShellStyle::Equals => ColorSpec::new()
.set_fg(Some(Color::Black))
.set_intense(true)
.clone(),
ShellStyle::Kind => ColorSpec::new().set_fg(Some(Color::Cyan)).clone(),
ShellStyle::Keyword => ColorSpec::new().set_fg(Some(Color::Magenta)).clone(),
ShellStyle::Primitive => ColorSpec::new()
.set_fg(Some(Color::Green))
.set_intense(true)
.clone(),
ShellStyle::Opaque => ColorSpec::new()
.set_fg(Some(Color::Yellow))
.set_intense(true)
.clone(),
ShellStyle::Description => ColorSpec::new()
.set_fg(Some(Color::Black))
.set_intense(true)
.clone(),
ShellStyle::Error => ColorSpec::new()
.set_fg(Some(Color::Red))
.set_intense(true)
.clone(),
}
}
}
#[derive(Clone, Copy, Eq, Ord, PartialEq, PartialOrd, Hash, new)]
pub struct ShellAnnotation {
style: ShellStyle,
}
impl std::fmt::Debug for ShellAnnotation {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self.style)
}
}
impl ShellAnnotation {
pub fn style(style: impl Into<ShellStyle>) -> ShellAnnotation {
ShellAnnotation {
style: style.into(),
}
}
}
pub type PrettyDebugDoc =
pretty::Doc<'static, pretty::BoxDoc<'static, ShellAnnotation>, ShellAnnotation>;
pub type PrettyDebugDocBuilder = pretty::DocBuilder<'static, pretty::BoxAllocator, ShellAnnotation>;
#[derive(Clone, new)]
pub struct DebugDocBuilder {
pub inner: PrettyDebugDocBuilder,
}
impl PrettyDebug for DebugDocBuilder {
fn pretty_debug(&self) -> DebugDocBuilder {
self.clone()
}
}
impl std::ops::Add for DebugDocBuilder {
type Output = DebugDocBuilder;
fn add(self, rhs: DebugDocBuilder) -> DebugDocBuilder {
DebugDocBuilder::new(self.inner.append(rhs.inner))
}
}
impl DebugDocBuilder {
pub fn from_doc(doc: DebugDoc) -> DebugDocBuilder {
DebugDocBuilder {
inner: BoxAllocator.nil().append(doc),
}
}
pub fn blank() -> DebugDocBuilder {
BoxAllocator.nil().into()
}
pub fn delimiter(string: impl std::fmt::Display) -> DebugDocBuilder {
DebugDocBuilder::styled(string, ShellStyle::Delimiter)
}
pub fn key(string: impl std::fmt::Display) -> DebugDocBuilder {
DebugDocBuilder::styled(string, ShellStyle::Key)
}
pub fn value(string: impl std::fmt::Display) -> DebugDocBuilder {
DebugDocBuilder::styled(string, ShellStyle::Value)
}
pub fn as_value(self) -> DebugDocBuilder {
self.inner
.annotate(ShellAnnotation::style(ShellStyle::Value))
.into()
}
pub fn equals() -> DebugDocBuilder {
DebugDocBuilder::styled("=", ShellStyle::Equals)
}
pub fn kind(string: impl std::fmt::Display) -> DebugDocBuilder {
DebugDocBuilder::styled(string, ShellStyle::Kind)
}
pub fn as_kind(self) -> DebugDocBuilder {
self.inner
.annotate(ShellAnnotation::style(ShellStyle::Kind))
.into()
}
pub fn keyword(string: impl std::fmt::Display) -> DebugDocBuilder {
DebugDocBuilder::styled(string, ShellStyle::Keyword)
}
pub fn primitive(string: impl std::fmt::Display) -> DebugDocBuilder {
DebugDocBuilder::styled(format!("{}", string), ShellStyle::Primitive)
}
pub fn opaque(string: impl std::fmt::Display) -> DebugDocBuilder {
DebugDocBuilder::styled(string, ShellStyle::Opaque)
}
pub fn description(string: impl std::fmt::Display) -> DebugDocBuilder {
DebugDocBuilder::styled(string, ShellStyle::Description)
}
pub fn error(string: impl std::fmt::Display) -> DebugDocBuilder {
DebugDocBuilder::styled(string, ShellStyle::Error)
}
pub fn delimit(start: &str, doc: DebugDocBuilder, end: &str) -> DebugDocBuilder {
DebugDocBuilder::delimiter(start) + doc + DebugDocBuilder::delimiter(end)
}
pub fn space() -> DebugDocBuilder {
BoxAllocator.space().into()
}
pub fn newline() -> DebugDocBuilder {
BoxAllocator.newline().into()
}
pub fn group(self) -> DebugDocBuilder {
self.inner.group().into()
}
pub fn nest(self) -> DebugDocBuilder {
self.inner.nest(1).group().into()
}
pub fn intersperse(
list: impl IntoIterator<Item = DebugDocBuilder>,
separator: DebugDocBuilder,
) -> DebugDocBuilder {
BoxAllocator.intersperse(list, separator).into()
}
pub fn list(list: impl IntoIterator<Item = DebugDocBuilder>) -> DebugDocBuilder {
let mut result: DebugDocBuilder = BoxAllocator.nil().into();
for item in list {
result = result + item;
}
result.into()
}
fn styled(string: impl std::fmt::Display, style: ShellStyle) -> DebugDocBuilder {
BoxAllocator
.text(string.to_string())
.annotate(ShellAnnotation::style(style))
.into()
}
}
impl std::ops::Deref for DebugDocBuilder {
type Target = PrettyDebugDocBuilder;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, new)]
pub struct DebugDoc {
pub inner: PrettyDebugDoc,
}
pub trait PrettyDebug {
fn pretty_debug(&self) -> DebugDocBuilder;
fn to_doc(&self) -> DebugDoc {
DebugDoc::new(self.pretty_doc())
}
fn pretty_doc(&self) -> PrettyDebugDoc {
let builder = self.pretty_debug();
builder.inner.into()
}
fn pretty_builder(&self) -> PrettyDebugDocBuilder {
let doc = self.pretty_debug();
doc.inner
}
fn plain_string(&self, width: usize) -> String {
let doc = self.pretty_doc();
let mut buffer = termcolor::Buffer::no_color();
doc.render_raw(
width,
&mut crate::parser::debug::TermColored::new(&mut buffer),
)
.unwrap();
String::from_utf8_lossy(buffer.as_slice()).to_string()
}
fn colored_string(&self, width: usize) -> String {
let doc = self.pretty_doc();
let mut buffer = termcolor::Buffer::ansi();
doc.render_raw(
width,
&mut crate::parser::debug::TermColored::new(&mut buffer),
)
.unwrap();
String::from_utf8_lossy(buffer.as_slice()).to_string()
}
}
impl Into<DebugDocBuilder> for PrettyDebugDocBuilder {
fn into(self) -> DebugDocBuilder {
DebugDocBuilder { inner: self }
}
}
impl std::ops::Deref for DebugDoc {
type Target = PrettyDebugDoc;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl From<DebugDoc> for PrettyDebugDoc {
fn from(input: DebugDoc) -> PrettyDebugDoc {
input.inner
}
}
impl Into<PrettyDebugDoc> for DebugDocBuilder {
fn into(self) -> PrettyDebugDoc {
self.inner.into()
}
}
fn hash_doc<H: std::hash::Hasher>(doc: &PrettyDebugDoc, state: &mut H) {
match doc {
pretty::Doc::Nil => 0u8.hash(state),
pretty::Doc::Append(a, b) => {
1u8.hash(state);
hash_doc(&*a, state);
hash_doc(&*b, state);
}
pretty::Doc::Group(a) => {
2u8.hash(state);
hash_doc(&*a, state);
}
pretty::Doc::Nest(a, b) => {
3u8.hash(state);
a.hash(state);
hash_doc(&*b, state);
}
pretty::Doc::Space => 4u8.hash(state),
pretty::Doc::Newline => 5u8.hash(state),
pretty::Doc::Text(t) => {
6u8.hash(state);
t.hash(state);
}
pretty::Doc::Annotated(a, b) => {
7u8.hash(state);
a.hash(state);
hash_doc(&*b, state);
}
}
}
impl std::hash::Hash for DebugDoc {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
hash_doc(&self.inner, state);
}
}
pub trait PrettyType {
fn pretty_type(&self) -> DebugDocBuilder;
}
pub trait FormatDebug: std::fmt::Debug {
fn fmt_debug(&self, f: &mut DebugFormatter, source: &str) -> fmt::Result;
fn fmt_display(&self, f: &mut DebugFormatter, source: &str) -> fmt::Result {
self.fmt_debug(f, source)
}
}
pub trait ToDebug: Sized + FormatDebug {

View File

@ -1,15 +1,16 @@
use crate::data::meta::Tagged;
use crate::data::Value;
use crate::errors::ShellError;
use crate::{PathMember, RawPathMember};
use std::fmt;
use std::ops::Div;
use std::path::{Component, Path, PathBuf};
pub fn did_you_mean(
obj_source: &Value,
field_tried: &Tagged<Value>,
) -> Option<Vec<(usize, String)>> {
let field_tried = field_tried.as_string().unwrap();
pub fn did_you_mean(obj_source: &Value, field_tried: &PathMember) -> Option<Vec<(usize, String)>> {
let field_tried = match &field_tried.item {
RawPathMember::String(string) => string.clone(),
RawPathMember::Int(int) => format!("{}", int),
};
let possibilities = obj_source.data_descriptors();
@ -25,11 +26,11 @@ pub fn did_you_mean(
if possible_matches.len() > 0 {
possible_matches.sort();
return Some(possible_matches);
}
Some(possible_matches)
} else {
None
}
}
pub struct AbsoluteFile {
inner: PathBuf,

View File

@ -203,9 +203,18 @@ fn errors_fetching_by_index_out_of_bounds() {
"#
));
assert!(actual.contains("Row not found"));
assert!(actual.contains("There isn't a row indexed at '3'"));
assert!(actual.contains("The table only has 3 rows (0..2)"))
assert!(
actual.contains("Row not found"),
format!("actual: {:?}", actual)
);
assert!(
actual.contains("There isn't a row indexed at 3"),
format!("actual: {:?}", actual)
);
assert!(
actual.contains("The table only has 3 rows (0 to 2)"),
format!("actual: {:?}", actual)
)
})
}
@ -214,10 +223,13 @@ fn requires_at_least_one_column_member_path() {
Playground::setup("get_test_9", |dirs, sandbox| {
sandbox.with_files(vec![EmptyFile("andres.txt")]);
let actual = nu_error!(
cwd: dirs.test(), "ls | get"
let actual = nu!(
cwd: dirs.test(), "ls | get | get type | echo $it"
);
assert!(actual.contains("requires member parameter"));
assert_eq!(
actual,
"[row: name, type, size, created, accessed, modified]"
);
})
}

View File

@ -189,7 +189,7 @@ fn open_can_parse_json() {
fn open_can_parse_xml() {
let actual = nu!(
cwd: "tests/fixtures/formats",
"open jonathan.xml | get rss.channel.item.link | echo $it"
"open jonathan.xml | get rss.channel | get item | get link | echo $it"
);
assert_eq!(

View File

@ -247,6 +247,80 @@ fn last_gets_last_row_when_no_amount_given() {
})
}
#[test]
fn get() {
Playground::setup("get_test_1", |dirs, sandbox| {
sandbox.with_files(vec![FileWithContent(
"sample.toml",
r#"
nu_party_venue = "zion"
"#,
)]);
let actual = nu!(
cwd: dirs.test(), h::pipeline(
r#"
open sample.toml
| get nu_party_venue
| echo $it
"#
));
assert_eq!(actual, "zion");
})
}
#[test]
fn get_more_than_one_member() {
Playground::setup("get_test_2", |dirs, sandbox| {
sandbox.with_files(vec![FileWithContent(
"sample.toml",
r#"
[[fortune_tellers]]
name = "Andrés N. Robalino"
arepas = 1
broken_builds = 0
[[fortune_tellers]]
name = "Jonathan Turner"
arepas = 1
broken_builds = 1
[[fortune_tellers]]
name = "Yehuda Katz"
arepas = 1
broken_builds = 1
"#,
)]);
let actual = nu!(
cwd: dirs.test(), h::pipeline(
r#"
open sample.toml
| get fortune_tellers
| get arepas broken_builds
| sum
| echo $it
"#
));
assert_eq!(actual, "5");
})
}
#[test]
fn get_requires_at_least_one_member() {
Playground::setup("first_test_3", |dirs, sandbox| {
sandbox.with_files(vec![EmptyFile("andres.txt")]);
let actual = nu!(
cwd: dirs.test(), "ls | get"
);
assert!(actual.contains("[row: name"), format!("{:?}", actual));
})
}
#[test]
fn lines() {
let actual = nu!(

View File

@ -525,8 +525,7 @@ fn can_convert_table_to_bson_and_back_into_table() {
| to-bson
| from-bson
| get root
| nth 1
| get b
| get 1.b
| echo $it
"#
));