diff --git a/Cargo.lock b/Cargo.lock index 7fe2bcd35..b4c5e79ea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml index 577a81066..cbf34bd1f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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"] diff --git a/Makefile.toml b/Makefile.toml index cb3060052..5b566acb2 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -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] diff --git a/src/cli.rs b/src/cli.rs index 57afc34e6..afdd95780 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -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> { 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> { 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> { let mut readline = Err(ReadlineError::Eof); while let Some(ref cmd) = initial_command { readline = rl.readline_with_initial(&prompt, (&cmd, "")); - initial_command = None; + 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> { } } - 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, 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 diff --git a/src/commands.rs b/src/commands.rs index 22a5f90b3..172ada176 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -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; diff --git a/src/commands/autoview.rs b/src/commands/autoview.rs index 774dfcf88..0d92553f5 100644 --- a/src/commands/autoview.rs +++ b/src/commands/autoview.rs @@ -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> = 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::>().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::>().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::>().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::>().await; } else { - println!("{:?}", item); + outln!("{:?}", item); } } } @@ -161,7 +182,7 @@ pub fn autoview( } } _ => { - //println!(""); + //outln!(""); } } diff --git a/src/commands/classified.rs b/src/commands/classified.rs index 0691a68a3..7dd72af4f 100644 --- a/src/commands/classified.rs +++ b/src/commands/classified.rs @@ -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 = 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; } } diff --git a/src/commands/command.rs b/src/commands/command.rs index 2dc69df9c..49166d915 100644 --- a/src/commands/command.rs +++ b/src/commands/command.rs @@ -68,7 +68,7 @@ impl CallInfo { #[derive(Getters)] #[get = "pub(crate)"] pub struct CommandArgs { - pub host: Arc>, + pub host: Arc>>, pub ctrl_c: Arc, 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>, + pub host: Arc>>, pub ctrl_c: Arc, 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, - ) -> Result, ShellError> { + callback: fn(T, RunnableContext) -> Result, + ) -> Result, 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>, + pub host: Arc>>, + pub source: Text, pub ctrl_c: Arc, pub commands: CommandRegistry, pub name: Tag, @@ -232,15 +245,15 @@ impl RunnablePerItemArgs { } } -pub struct RunnableArgs { +pub struct RunnableArgs { args: T, context: RunnableContext, - callback: fn(T, RunnableContext) -> Result, + callback: fn(T, RunnableContext) -> Result, } -impl RunnableArgs { +impl RunnableArgs { pub fn run(self) -> Result { - (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), EnterHelpShell(Tagged), @@ -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), + DebugValue(Tagged), 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>) -> ReturnValue { + Ok(ReturnSuccess::DebugValue(input.into())) + } + pub fn action(input: CommandAction) -> ReturnValue { Ok(ReturnSuccess::Action(input)) } diff --git a/src/commands/debug.rs b/src/commands/debug.rs index 80da5fe1c..cee07a0b4 100644 --- a/src/commands/debug.rs +++ b/src/commands/debug.rs @@ -32,7 +32,7 @@ pub fn debug(args: CommandArgs, _registry: &CommandRegistry) -> Result &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 { + args.process(registry, debug_value)?.run() + } +} + +fn debug_value( + _args: DebugArgs, + RunnableContext { mut input, .. }: RunnableContext, +) -> Result { + let stream = async_stream! { + while let Some(row) = input.values.next().await { + yield ReturnSuccess::debug_value(row.clone()) + } + }; + + Ok(stream) +} diff --git a/src/commands/evaluate_by.rs b/src/commands/evaluate_by.rs index f4925917c..05311db70 100644 --- a/src/commands/evaluate_by.rs +++ b/src/commands/evaluate_by.rs @@ -71,7 +71,11 @@ fn fetch( ) -> Box, Tag) -> Option> + 'static> { Box::new(move |value: Tagged, 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 diff --git a/src/commands/format.rs b/src/commands/format.rs index 0c4127559..fb5b451d5 100644 --- a/src/commands/format.rs +++ b/src/commands/format.rs @@ -10,7 +10,7 @@ pub(crate) fn format(input: Vec, host: &mut dyn Host) { crate::format::print_view(&view, &mut *host); if last != i { - println!(""); + outln!(""); } } } diff --git a/src/commands/get.rs b/src/commands/get.rs index 790c44498..9b17f8912 100644 --- a/src/commands/get.rs +++ b/src/commands/get.rs @@ -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, } @@ -19,16 +21,10 @@ impl WholeStreamCommand for Get { } fn signature(&self) -> Signature { - Signature::build("get") - .required( - "member", - SyntaxShape::ColumnPath, - "the path to the data to get", - ) - .rest( - SyntaxShape::ColumnPath, - "optionally return additional data by path", - ) + Signature::build("get").rest( + SyntaxShape::ColumnPath, + "optionally return additional data by path", + ) } fn usage(&self) -> &str { @@ -44,8 +40,6 @@ impl WholeStreamCommand for Get { } } -pub type ColumnPath = Vec>; - pub fn get_column_path( path: &ColumnPath, obj: &Tagged, @@ -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)), - .. - } => { + match did_you_mean(&obj_source, column_path_tried) { + Some(suggestions) => { return ShellError::labeled_error( - "No rows available", - format!( - "Not a table. Perhaps you meant to get the column '{}' instead?", - index - ), - column_path_tried.tag(), + "Unknown column", + format!("did you mean '{}'?", suggestions[0].1), + span_for_spanned_list(fields.members().iter().map(|p| p.span())), ) } - _ => 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())), - ) - } - }, + 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,54 +100,69 @@ pub fn get_column_path( } pub fn get( - GetArgs { - member, - rest: fields, - }: GetArgs, + GetArgs { rest: mut fields }: GetArgs, RunnableContext { input, .. }: RunnableContext, ) -> Result { - trace!("get {:?} {:?}", member, fields); + if fields.len() == 0 { + let stream = async_stream! { + let values = input.values; + pin_mut!(values); - let stream = input - .values - .map(move |item| { - let mut result = VecDeque::new(); + let mut shapes = Shapes::new(); + let mut index = 0; - let member = vec![member.clone()]; - - let column_paths = vec![&member, &fields] - .into_iter() - .flatten() - .collect::>(); - - for path in column_paths { - let res = get_column_path(&path, &item); - - match res { - Ok(got) => match got { - Tagged { - 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), - )) - } - } - other => result - .push_back(ReturnSuccess::value((*other).clone().tagged(&item.tag))), - }, - Err(reason) => result.push_back(Err(reason)), - } + while let Some(row) = values.next().await { + shapes.add(&row.item, index); + index += 1; } - result - }) - .flatten(); - Ok(stream.to_output_stream()) + 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| { + let mut result = VecDeque::new(); + + let member = vec![member.clone()]; + + let column_paths = vec![&member, &fields] + .into_iter() + .flatten() + .collect::>(); + + for path in column_paths { + let res = get_column_path(&path, &item); + + match res { + Ok(got) => match got { + Tagged { + item: Value::Table(rows), + .. + } => { + for item in rows { + result.push_back(ReturnSuccess::value(item.clone())); + } + } + other => result.push_back(ReturnSuccess::value(other.clone())), + }, + Err(reason) => result + .push_back(ReturnSuccess::value(Value::Error(reason).tagged_unknown())), + } + } + + result + }) + .flatten(); + + Ok(stream.to_output_stream()) + } } diff --git a/src/commands/group_by.rs b/src/commands/group_by.rs index 07e74841b..66bbb4271 100644 --- a/src/commands/group_by.rs +++ b/src/commands/group_by.rs @@ -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(); diff --git a/src/commands/help.rs b/src/commands/help.rs index 596bcbf93..487313352 100644 --- a/src/commands/help.rs +++ b/src/commands/help.rs @@ -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())); diff --git a/src/commands/histogram.rs b/src/commands/histogram.rs index 6933f28a6..bff24aa02 100644 --- a/src/commands/histogram.rs +++ b/src/commands/histogram.rs @@ -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 = 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::()); diff --git a/src/commands/lines.rs b/src/commands/lines.rs index 8375098b7..cf8bfc9b6 100644 --- a/src/commands/lines.rs +++ b/src/commands/lines.rs @@ -35,8 +35,6 @@ fn lines(args: CommandArgs, registry: &CommandRegistry) -> Result Result { if let Ok(s) = x.as_string() { headers.push(s); @@ -115,7 +115,7 @@ pub fn pivot(args: PivotArgs, context: RunnableContext) -> Result { dict.insert_tagged(headers[column_num].clone(), x.clone()); } diff --git a/src/commands/plugin.rs b/src/commands/plugin.rs index 5dfbe6be5..c9cc514af 100644 --- a/src/commands/plugin.rs +++ b/src/commands/plugin.rs @@ -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 diff --git a/src/commands/sort_by.rs b/src/commands/sort_by.rs index d384207c9..da872b9b3 100644 --- a/src/commands/sort_by.rs +++ b/src/commands/sort_by.rs @@ -40,7 +40,7 @@ fn sort_by( let calc_key = |item: &Tagged| { 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.sort_by_cached_key(calc_key); diff --git a/src/commands/split_by.rs b/src/commands/split_by.rs index 1f972a2c5..3744d7c78 100644 --- a/src/commands/split_by.rs +++ b/src/commands/split_by.rs @@ -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(), )) } } diff --git a/src/commands/t_sort_by.rs b/src/commands/t_sort_by.rs index 1c914dbac..3cfcae71f 100644 --- a/src/commands/t_sort_by.rs +++ b/src/commands/t_sort_by.rs @@ -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, value: &Tagged, tag: impl Into, -) -> Vec> { +) -> Vec> { let origin_tag = tag.into(); match value { @@ -110,22 +110,20 @@ pub fn columns_sorted( keys.sort(); - let keys: Vec = keys + let keys: Vec = 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(), - }) + .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> = + 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> = match &dataset { Tagged { item: Value::Row(rows), .. } => { - let mut keys: Vec> = rows + let mut keys: Vec> = 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::::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 = 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>> = 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 { - Some(Tagged { - item: Value::Row(dict), - .. - }) => dict.get_data_by_key(&label).unwrap().clone(), - _ => Value::Table(vec![]).tagged(&origin_tag), - } + .map(|label| match &groups { + Some(Tagged { + item: Value::Row(dict), + .. + }) => 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() ] ) } diff --git a/src/commands/to_bson.rs b/src/commands/to_bson.rs index afcd198cf..c4685be34 100644 --- a/src/commands/to_bson.rs +++ b/src/commands/to_bson.rs @@ -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) -> Result { } 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::, 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) -> Result Err(ShellError::type_error( "bson binary", - tagged_value.tagged_type_name(), + tagged_value.type_name().spanned(tagged_value.span()), )), } } diff --git a/src/commands/to_delimited_data.rs b/src/commands/to_delimited_data.rs index 68d2ecb94..e5924b12d 100644 --- a/src/commands/to_delimited_data.rs +++ b/src/commands/to_delimited_data.rs @@ -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, @@ -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) -> Result { } } -fn merge_descriptors(values: &[Tagged]) -> Vec { - let mut ret = vec![]; +fn merge_descriptors(values: &[Tagged]) -> Vec> { + let mut ret: Vec> = vec![]; + let mut seen: IndexSet = 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)); } } } diff --git a/src/commands/to_json.rs b/src/commands/to_json.rs index 62a347630..b100596ab 100644 --- a/src/commands/to_json.rs +++ b/src/commands/to_json.rs @@ -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) -> Result 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::::coerce_into( + int.tagged(&v.tag), + "converting to JSON number", + )?), + )), + }) + .collect::, ShellError>>()?, + ), Value::Primitive(Primitive::Path(s)) => serde_json::Value::String(s.display().to_string()), Value::Table(l) => serde_json::Value::Array(json_list(l)?), diff --git a/src/commands/to_sqlite.rs b/src/commands/to_sqlite.rs index 2bc7a6a67..e5fcf3e27 100644 --- a/src/commands/to_sqlite.rs +++ b/src/commands/to_sqlite.rs @@ -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)); } } diff --git a/src/commands/to_toml.rs b/src/commands/to_toml.rs index 92e2c54ea..3ee1562cc 100644 --- a/src/commands/to_toml.rs +++ b/src/commands/to_toml.rs @@ -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) -> Result 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::, ShellError>>()?, + ), Value::Table(l) => toml::Value::Array(collect_values(l)?), Value::Error(e) => return Err(e.clone()), diff --git a/src/commands/to_yaml.rs b/src/commands/to_yaml.rs index 737ef81f8..4c12c30e7 100644 --- a/src/commands/to_yaml.rs +++ b/src/commands/to_yaml.rs @@ -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) -> Result 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::::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) => { diff --git a/src/commands/what.rs b/src/commands/what.rs new file mode 100644 index 000000000..c6d2ebfb5 --- /dev/null +++ b/src/commands/what.rs @@ -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 { + args.process(registry, what)?.run() + } +} + +pub fn what( + WhatArgs {}: WhatArgs, + RunnableContext { input, host, .. }: RunnableContext, +) -> Result { + 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)) +} diff --git a/src/context.rs b/src/context.rs index 70c0485d3..80fa536e5 100644 --- a/src/context.rs +++ b/src/context.rs @@ -64,7 +64,8 @@ impl CommandRegistry { #[derive(Clone)] pub struct Context { registry: CommandRegistry, - host: Arc>, + host: Arc>>, + pub current_errors: Arc>>, pub ctrl_c: Arc, 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(&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(&mut self, block: impl FnOnce(&mut Vec) -> T) -> T { + let mut errors = self.current_errors.lock().unwrap(); + + block(&mut *errors) + } + pub fn add_commands(&mut self, commands: Vec>) { for command in commands { self.registry.insert(command.name().to_string(), command); diff --git a/src/data/base.rs b/src/data/base.rs index ed34c9dfd..0579e7017 100644 --- a/src/data/base.rs +++ b/src/data/base.rs @@ -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), @@ -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", + Primitive::EndOfStream => "marker", + } + } +} + impl From for Primitive { fn from(decimal: BigDecimal) -> Primitive { Primitive::Decimal(decimal) @@ -102,47 +129,6 @@ impl From 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) -> 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, 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 for Number { fn into(self) -> Value { match self { @@ -288,41 +304,16 @@ impl Into for &Number { } } -pub fn debug_list(values: &Vec>) -> ValuesDebug<'_> { - ValuesDebug { values } -} - -pub struct ValuesDebug<'a> { - values: &'a Vec>, -} - -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, -} - -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 { pub fn tagged_type_name(&self) -> Tagged { - 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 { + let name = self.type_name().to_string(); name.tagged(self.tag()) } } @@ -335,7 +326,7 @@ impl std::convert::TryFrom<&Tagged> 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> 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> 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> for Vec { 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> 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>> 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 { - pub(crate) fn debug(&self) -> ValueDebug<'_> { - ValueDebug { value: self } - } - - pub fn as_column_path(&self) -> Result>>, 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 { - 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 { match self { Value::Primitive(_) => vec![], @@ -485,201 +429,6 @@ impl Value { } } - pub(crate) fn get_data_by_index(&self, idx: usize) -> Option<&Tagged> { - match self { - Value::Table(value_set) => value_set.get(idx), - _ => None, - } - } - - pub(crate) fn get_data_by_key(&self, name: &str) -> Option<&Tagged> { - 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> { - 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>, - callback: Box)) -> ShellError>, - ) -> Result>, 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::() { - 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>, - new_value: Value, - ) -> Option> { - 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::>>(); - - 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>, - replaced_value: Value, - ) -> Option> { - 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::>>(); - - 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) -> 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 { + ) -> Result { 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>) -> Value { Value::Row(entries.into()) @@ -766,6 +524,16 @@ impl Value { Value::Primitive(Primitive::String(s.into())) } + pub fn column_path(s: Vec>) -> Value { + Value::Primitive(Primitive::ColumnPath(ColumnPath::new( + s.into_iter().map(|p| p.into()).collect(), + ))) + } + + pub fn int(i: impl Into) -> Value { + Value::Primitive(Primitive::Int(i.into())) + } + pub fn pattern(s: impl Into) -> 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) -> Value { - Value::Primitive(Primitive::Int(s.into())) - } - pub fn decimal(s: impl Into) -> Value { Value::Primitive(Primitive::Decimal(s.into())) } @@ -837,7 +601,7 @@ impl Tagged { 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 { +fn coerce_compare( + left: &Value, + right: &Value, +) -> Result { 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 Result { +) -> Result { 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) -> Tagged { Value::string(input.into()).tagged_unknown() } - fn number(n: i64) -> Tagged { - Value::number(n).tagged_unknown() + fn int(input: impl Into) -> Tagged { + Value::int(input.into()).tagged_unknown() } fn row(entries: IndexMap>) -> Tagged { @@ -961,12 +731,16 @@ mod tests { Value::table(list).tagged_unknown() } - fn error_callback() -> impl FnOnce((Value, Tagged)) -> 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>) -> Vec> { - table(paths).as_column_path().unwrap().item + fn column_path(paths: &Vec>) -> Tagged { + 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!( diff --git a/src/data/base/debug.rs b/src/data/base/debug.rs new file mode 100644 index 000000000..ccac2eef9 --- /dev/null +++ b/src/data/base/debug.rs @@ -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 { + 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) -> DebugDocBuilder { + b::primitive(format!("{:?}", name)) + b::delimit("(", b::kind(ty.into()), ")") +} diff --git a/src/data/base/property_get.rs b/src/data/base/property_get.rs new file mode 100644 index 000000000..587a2f474 --- /dev/null +++ b/src/data/base/property_get.rs @@ -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 { + pub(crate) fn get_data_by_member( + &self, + name: &PathMember, + ) -> Result, 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 ShellError>, + ) -> Result, 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((¤t.clone(), &p.clone(), e))), + } + } + + Ok(current) + } + + pub fn insert_data_at_path(&self, path: &str, new_value: Value) -> Option> { + 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, + ) -> 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, + ) -> Result, ShellError> { + let (last, front) = split_path.split_last(); + let mut original = self.clone(); + + let mut current: &mut Tagged = &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> { + let mut new_obj: Tagged = 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, ShellError> { + match &self.item { + Value::Table(table) => { + let mut out: Vec = 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 { + 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 { + 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>, + index: Tagged, + new_value: Tagged, +) -> 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) -> Option> { + 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> { + 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> { + 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, + } + } +} diff --git a/src/data/base/shape.rs b/src/data/base/shape.rs new file mode 100644 index 000000000..751b141f4 --- /dev/null +++ b/src/data/base/shape.rs @@ -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), + Table(Vec), + + // 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) -> 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 => "".to_string(), + }) + b::delimit("(", ty.pretty_debug(), ")").as_kind()) + .nest() + }), + b::space(), + ) + .nest()) + .nest(), + + TypeShape::Table(table) => { + let mut group: Group> = 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!(""), + }) + 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), + Duration(u64), + Path(PathBuf), + Binary, + + Row(BTreeMap), + Table(Vec), + + // 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, +} + +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) -> 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) -> 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(""), + 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 { + values: indexmap::IndexMap, +} + +impl Group +where + K: Debug + Eq + Hash, + G: GroupedValue, +{ + pub fn new() -> Group { + Group { + values: indexmap::IndexMap::default(), + } + } + + pub fn len(&self) -> usize { + self.values.len() + } + + pub fn into_iter(self) -> impl Iterator { + self.values.into_iter() + } + + pub fn add(&mut self, key: impl Into, value: impl Into) { + 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 for String { + fn into(self) -> Column { + Column::String(self) + } +} + +impl Into for &String { + fn into(self) -> Column { + Column::String(self.clone()) + } +} + +impl Into 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), + 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!(""), + }) + .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!(""), + }) + .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>, +} + +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> { + 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() + } + } +} diff --git a/src/data/config.rs b/src/data/config.rs index 26e3e3c7d..6ff26efcf 100644 --- a/src/data/config.rs +++ b/src/data/config.rs @@ -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), )), } } diff --git a/src/data/dict.rs b/src/data/dict.rs index 32393f0a0..9cdc07083 100644 --- a/src/data/dict.rs +++ b/src/data/dict.rs @@ -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>, } +#[derive(Debug, new)] +struct DebugEntry<'a> { + key: &'a str, + value: &'a Tagged, +} + +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 { 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> { - match self + pub fn keys(&self) -> impl Iterator { + self.entries.keys() + } + + pub(crate) fn get_data_by_key(&self, name: Spanned<&str>) -> Option> { + 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> { @@ -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) { + self.entries.insert(name.to_string(), value); } } @@ -158,6 +213,12 @@ impl TaggedDictBuilder { } } + pub fn build(tag: impl Into, block: impl FnOnce(&mut TaggedDictBuilder)) -> Tagged { + let mut builder = TaggedDictBuilder::new(tag); + block(&mut builder); + builder.into_tagged_value() + } + pub fn with_capacity(tag: impl Into, n: usize) -> TaggedDictBuilder { TaggedDictBuilder { tag: tag.into(), diff --git a/src/data/meta.rs b/src/data/meta.rs index 6c9294c57..5a12df2fe 100644 --- a/src/data/meta.rs +++ b/src/data/meta.rs @@ -23,6 +23,21 @@ impl Spanned { } } +impl Spanned { + pub fn items<'a, U>( + items: impl Iterator>, + ) -> impl Iterator { + items.into_iter().map(|item| &item.item[..]) + } +} + +impl Spanned { + 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) -> Spanned { Spanned { @@ -53,6 +68,17 @@ pub struct Tagged { pub item: T, } +impl Tagged { + 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 HasTag for Tagged { fn tag(&self) -> Tag { self.tag.clone() @@ -116,6 +142,13 @@ impl Tagged { } } + 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) -> Tag { } } +#[allow(unused)] +pub fn span_for_spanned_list(mut iter: impl Iterator) -> 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, diff --git a/src/data/types.rs b/src/data/types.rs index b4ff545de..9ac4fc139 100644 --- a/src/data/types.rs +++ b/src/data/types.rs @@ -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()), + )), } } } diff --git a/src/env/host.rs b/src/env/host.rs index 268f87aff..bd3bd17d5 100644 --- a/src/env/host.rs +++ b/src/env/host.rs @@ -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 { @@ -37,6 +39,10 @@ impl Host for Box { 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( diff --git a/src/errors.rs b/src/errors.rs index c28658028..99e6bef72 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -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), + Source(Spanned), Synthetic(String), } +impl> Into for Spanned { + fn into(self) -> Description { + Description::Source(self.map(|s| s.into())) + } +} + impl Description { fn into_label(self) -> Result, 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, + actual: Spanned, }, ArgumentError { - command: String, + command: Spanned, 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>) -> ParseError { - let Tagged { tag, item } = actual; + pub fn mismatch(expected: &'static str, actual: Spanned>) -> 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, - kind: ArgumentError, - tag: impl Into, - ) -> ParseError { - let tag = tag.into(); - + pub fn argument_error(command: Spanned>, 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 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>, } -impl ShellError { - #[allow(unused)] - pub(crate) fn tag(&self) -> Option { - 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, - actual: Tagged>, + actual: Spanned>, ) -> ShellError { ProximateShellError::TypeError { expected: expected.into(), @@ -154,6 +133,28 @@ impl ShellError { .start() } + pub fn missing_property( + subpath: Spanned>, + expr: Spanned>, + ) -> ShellError { + ProximateShellError::MissingProperty { + subpath: subpath.into(), + expr: expr.into(), + } + .start() + } + + pub fn invalid_integer_index( + subpath: Spanned>, + integer: impl Into, + ) -> ShellError { + ProximateShellError::InvalidIntegerIndex { + subpath: subpath.into(), + integer: integer.into(), + } + .start() + } + pub fn untagged_runtime_error(error: impl Into) -> ShellError { ProximateShellError::UntaggedRuntimeError { reason: error.into(), @@ -161,10 +162,10 @@ impl ShellError { .start() } - pub(crate) fn unexpected_eof(expected: impl Into, tag: impl Into) -> ShellError { + pub(crate) fn unexpected_eof(expected: impl Into, span: impl Into) -> 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, actual: &Tagged, - operation: String, + operation: impl Into, ) -> 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>) -> ShellError { + pub(crate) fn syntax_error(problem: Spanned>) -> ShellError { ProximateShellError::SyntaxError { problem: problem.map(|p| p.into()), } .start() } - #[allow(unused)] - pub(crate) fn invalid_command(problem: impl Into) -> ShellError { - ProximateShellError::InvalidCommand { - command: problem.into(), - } - .start() - } - pub(crate) fn coerce_error( - left: Tagged>, - right: Tagged>, + left: Spanned>, + right: Spanned>, ) -> ShellError { ProximateShellError::CoerceError { left: left.map(|l| l.into()), @@ -208,23 +201,21 @@ impl ShellError { .start() } - pub(crate) fn missing_value(tag: Option, reason: impl Into) -> ShellError { + pub(crate) fn missing_value(span: Option, reason: impl Into) -> ShellError { ProximateShellError::MissingValue { - tag, + span, reason: reason.into(), } .start() } pub(crate) fn argument_error( - command: impl Into, + command: Spanned>, kind: ArgumentError, - tag: impl Into, ) -> 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 { 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> for ExpectedRange { + fn from(range: Range) -> 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, + problem: Spanned, }, UnexpectedEof { expected: String, - tag: Tag, - }, - InvalidCommand { - command: Tag, + span: Span, }, TypeError { expected: String, - actual: Tagged>, + actual: Spanned>, }, MissingProperty { subpath: Description, expr: Description, - tag: Tag, + }, + InvalidIntegerIndex { + subpath: Description, + integer: Span, }, MissingValue { - tag: Option, + span: Option, reason: String, }, ArgumentError { - command: String, + command: Spanned, error: ArgumentError, - tag: Tag, }, RangeError { kind: ExpectedRange, - actual_kind: Tagged, + actual_kind: Spanned, operation: String, }, Diagnostic(ShellDiagnostic), CoerceError { - left: Tagged, - right: Tagged, + left: Spanned, + right: Spanned, }, UntaggedRuntimeError { reason: String, @@ -539,21 +555,22 @@ impl ProximateShellError { } } - pub(crate) fn tag(&self) -> Option { - 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 { + // 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, } +impl std::hash::Hash for ShellDiagnostic { + fn hash(&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"), diff --git a/src/evaluate/evaluator.rs b/src/evaluate/evaluator.rs index aee48f2e8..1930b64c8 100644 --- a/src/evaluate/evaluator.rs +++ b/src/evaluate/evaluator.rs @@ -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,35 +104,33 @@ 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(); - let mut possible_matches: Vec<_> = possibilities - .iter() - .map(|x| (natural::distance::levenshtein_distance(x, &name), x)) - .collect(); + if let RawPathMember::String(name) = &member.item { + let mut possible_matches: Vec<_> = possibilities + .iter() + .map(|x| (natural::distance::levenshtein_distance(x, &name), x)) + .collect(); - possible_matches.sort(); + possible_matches.sort(); - if possible_matches.len() > 0 { - return Err(ShellError::labeled_error( - "Unknown column", - format!("did you mean '{}'?", possible_matches[0].1), - &tag, - )); - } else { - return Err(ShellError::labeled_error( - "Unknown column", - "row does not have this column", - &tag, - )); + if possible_matches.len() > 0 { + return Err(ShellError::labeled_error( + "Unknown column", + format!("did you mean '{}'?", possible_matches[0].1), + &tag, + )); + } else { + 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 { 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, 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, ShellError> { - Err(ShellError::syntax_error("Unexpected command".tagged(tag))) + Err(ShellError::syntax_error( + "Unexpected command".spanned(tag.span), + )) } diff --git a/src/format/entries.rs b/src/format/entries.rs index db0cb52c8..7dd891bd9 100644 --- a/src/format/entries.rs +++ b/src/format/entries.rs @@ -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(()) diff --git a/src/format/generic.rs b/src/format/generic.rs index fd058f31f..d9dc8ead2 100644 --- a/src/format/generic.rs +++ b/src/format/generic.rs @@ -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(()) diff --git a/src/format/table.rs b/src/format/table.rs index 3ed5937b4..3c0ade3c5 100644 --- a/src/format/table.rs +++ b/src/format/table.rs @@ -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 - .iter() - .enumerate() - .map(|(i, d)| { - if d == "" { - 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])), - data.borrow().style_leaf(), - ) - } - _ => ( - Value::nothing().format_leaf(None), - Value::nothing().style_leaf(), - ), - } - } - }) - .collect(); + let mut row: Vec<(DebugDocBuilder, &'static str)> = match value { + Tagged { + item: Value::Row(..), + .. + } => headers + .iter() + .enumerate() + .map(|(i, d)| { + let data = value.get_data(d); + return ( + data.borrow().format_for_column(&headers[i]), + data.borrow().style_leaf(), + ); + }) + .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::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 { - 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); + if true_width(&headers[head]) > max_column_width { + headers[head] = fill(&headers[head], 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 { diff --git a/src/fuzzysearch.rs b/src/fuzzysearch.rs new file mode 100644 index 000000000..bbcfdc5c9 --- /dev/null +++ b/src/fuzzysearch.rs @@ -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 { + 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::>(); + matches.sort_by(|a, b| b.1.score().cmp(&a.1.score())); + + let results: Vec = 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 { + 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, 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"); +} diff --git a/src/lib.rs b/src/lib.rs index f0b810220..9abd82b2e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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; diff --git a/src/parser.rs b/src/parser.rs index 7acdf6e6b..003b8426c 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,3 +1,4 @@ +pub(crate) mod debug; pub(crate) mod deserializer; pub(crate) mod hir; pub(crate) mod parse; diff --git a/src/parser/debug.rs b/src/parser/debug.rs new file mode 100644 index 000000000..739d2264d --- /dev/null +++ b/src/parser/debug.rs @@ -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, + 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 { + 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 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(), + } + } +} diff --git a/src/parser/deserializer.rs b/src/parser/deserializer.rs index c9436ab29..0c027c709 100644 --- a/src/parser/deserializer.rs +++ b/src/parser/deserializer.rs @@ -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(self, _visitor: V) -> Result @@ -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(mut self, len: usize, visitor: V) -> Result @@ -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::>(); 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::(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::(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::, _>(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( diff --git a/src/parser/hir.rs b/src/parser/hir.rs index 28b8a21a0..0ef8117b4 100644 --- a/src/parser/hir.rs +++ b/src/parser/hir.rs @@ -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, span: impl Into) -> Expression { + RawExpression::Literal(Literal::ColumnPath(members)).spanned(span.into()) + } + pub(crate) fn path( head: Expression, - tail: Vec>>, + tail: Vec>, span: impl Into, ) -> 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>) -> Expression { + pub(crate) fn dot_member(head: Expression, next: impl Into) -> 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, outer: impl Into) -> 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 { @@ -301,6 +305,7 @@ pub enum Literal { Size(Number, Unit), String(Span), GlobPattern(String), + ColumnPath(Vec), 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", } diff --git a/src/parser/hir/baseline_parse/tests.rs b/src/parser/hir/baseline_parse/tests.rs index 6ca653c1b..5e7775bd6 100644 --- a/src/parser/hir/baseline_parse/tests.rs +++ b/src/parser/hir/baseline_parse/tests.rs @@ -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); diff --git a/src/parser/hir/expand_external_tokens.rs b/src/parser/hir/expand_external_tokens.rs index e99147c22..c5634ea0e 100644 --- a/src/parser/hir/expand_external_tokens.rs +++ b/src/parser/hir/expand_external_tokens.rs @@ -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), )) } }), diff --git a/src/parser/hir/path.rs b/src/parser/hir/path.rs index 4a9907475..29685d0cf 100644 --- a/src/parser/hir/path.rs +++ b/src/parser/hir/path.rs @@ -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; + +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, +} + +impl ColumnPath { + pub fn iter(&self) -> impl Iterator { + 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 = 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 { + 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, span: impl Into) -> PathMember { + RawPathMember::String(string.into()).spanned(span.into()) + } + + pub fn int(int: impl Into, span: impl Into) -> 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>, + tail: Vec, } impl fmt::Display for Path { @@ -39,7 +136,7 @@ impl fmt::Display for Path { } impl Path { - pub(crate) fn parts(self) -> (Expression, Vec>) { + pub(crate) fn parts(self) -> (Expression, Vec) { (self.head, self.tail) } } diff --git a/src/parser/hir/syntax_shape.rs b/src/parser/hir/syntax_shape.rs index 32c54bc4e..241992491 100644 --- a/src/parser/hir/syntax_shape.rs +++ b/src/parser/hir/syntax_shape.rs @@ -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 ExpandExpression for SpacedExpression { 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()), )), } } diff --git a/src/parser/hir/syntax_shape/block.rs b/src/parser/hir/syntax_shape/block.rs index b5059bcb7..2939fa5ab 100644 --- a/src/parser/hir/syntax_shape/block.rs +++ b/src/parser/hir/syntax_shape/block.rs @@ -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>, ) -> 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>, ) -> 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 { - // A shorthand path must not be at EOF - let peeked = token_nodes.peek_non_ws().not_eof("shorthand path")?; + let head = expand_syntax(&MemberShape, token_nodes, context)?; + let head = head.to_path_member(context.source); - 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(); + // Synthesize an `$it` expression + let it = synthetic_it(); + let span = head.span; - // 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![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)) } } diff --git a/src/parser/hir/syntax_shape/expression.rs b/src/parser/hir/syntax_shape/expression.rs index 6429ab57c..4870f11b1 100644 --- a/src/parser/hir/syntax_shape/expression.rs +++ b/src/parser/hir/syntax_shape/expression.rs @@ -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(), )) } } diff --git a/src/parser/hir/syntax_shape/expression/atom.rs b/src/parser/hir/syntax_shape/expression/atom.rs index 09583c728..ff40dc59f 100644 --- a/src/parser/hir/syntax_shape/expression/atom.rs +++ b/src/parser/hir/syntax_shape/expression/atom.rs @@ -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>; 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, 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), )) } diff --git a/src/parser/hir/syntax_shape/expression/number.rs b/src/parser/hir/syntax_shape/expression/number.rs index 492a29202..e8e3d5940 100644 --- a/src/parser/hir/syntax_shape/expression/number.rs +++ b/src/parser/hir/syntax_shape/expression/number.rs @@ -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), diff --git a/src/parser/hir/syntax_shape/expression/pattern.rs b/src/parser/hir/syntax_shape/expression/pattern.rs index 2ccd5a4f0..02d5ab274 100644 --- a/src/parser/hir/syntax_shape/expression/pattern.rs +++ b/src/parser/hir/syntax_shape/expression/pattern.rs @@ -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), + )), } }) } diff --git a/src/parser/hir/syntax_shape/expression/unit.rs b/src/parser/hir/syntax_shape/expression/unit.rs index 901b86e8d..19091fa6f 100644 --- a/src/parser/hir/syntax_shape/expression/unit.rs +++ b/src/parser/hir/syntax_shape/expression/unit.rs @@ -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), }; diff --git a/src/parser/hir/syntax_shape/expression/variable_path.rs b/src/parser/hir/syntax_shape/expression/variable_path.rs index f6b4c1931..300b81c0d 100644 --- a/src/parser/hir/syntax_shape/expression/variable_path.rs +++ b/src/parser/hir/syntax_shape/expression/variable_path.rs @@ -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> = vec![]; + let mut tail: Vec = 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> { + 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>>; + type Output = Spanned>; fn name(&self) -> &'static str { "path continuation" @@ -219,7 +233,7 @@ impl ExpandSyntax for PathTailShape { context: &ExpandContext, ) -> Result { let mut end: Option = None; - let mut tail = vec![]; + let mut tail: Vec = 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), + DotSuffix(Span, PathMember), InfixSuffix(Spanned, 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 { - 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> { } } -#[derive(Debug, Clone, Getters, new)] -pub struct ColumnPath { - #[get = "pub"] - path: Tagged>, -} - -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>; fn name(&self) -> &'static str { "column path" @@ -813,7 +812,7 @@ impl ExpandSyntax for ColumnPathShape { token_nodes: &'b mut TokensIterator<'a>, context: &ExpandContext, ) -> Result { - 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 { + 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 { + 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), )), } }, diff --git a/src/parser/hir/tokens_iterator.rs b/src/parser/hir/tokens_iterator.rs index bba5ff135..097b403f3 100644 --- a/src/parser/hir/tokens_iterator.rs +++ b/src/parser/hir/tokens_iterator.rs @@ -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( &mut self, desc: &'static str, - block: impl FnOnce(&mut TokensIterator) -> Result, + block: impl FnOnce(&mut TokensIterator<'content>) -> Result, ) -> Result 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, + ) -> Result { + 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. diff --git a/src/parser/parse/parser.rs b/src/parser/parse/parser.rs index 0dd1bc856..e5fe60559 100644 --- a/src/parser/parse/parser.rs +++ b/src/parser/parse/parser.rs @@ -164,6 +164,12 @@ impl Into for BigDecimal { } } +impl Into for BigInt { + fn into(self) -> Number { + Number::Int(self) + } +} + #[tracable_parser] pub fn number(input: NomSpan) -> IResult { let (input, number) = raw_number(input)?; diff --git a/src/parser/parse/token_tree.rs b/src/parser/parse/token_tree.rs index 137b22be7..12c33d2eb 100644 --- a/src/parser/parse/token_tree.rs +++ b/src/parser/parse/token_tree.rs @@ -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 { 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()), + )), } } diff --git a/src/parser/parse_command.rs b/src/parser/parse_command.rs index cb5111179..79ec0dd07 100644 --- a/src/parser/parse_command.rs +++ b/src/parser/parse_command.rs @@ -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)) => { diff --git a/src/parser/registry.rs b/src/parser/registry.rs index 11f9d6baa..d31a023b0 100644 --- a/src/parser/registry.rs +++ b/src/parser/registry.rs @@ -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>>, -} - -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>>, -} - -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> { match &self.positional { None => None, diff --git a/src/plugin.rs b/src/plugin.rs index 004e937fe..c4f56584d 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -143,8 +143,8 @@ fn send_response(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)] diff --git a/src/plugins/binaryview.rs b/src/plugins/binaryview.rs index 4e563beab..274a8a22f 100644 --- a/src/plugins/binaryview.rs +++ b/src/plugins/binaryview.rs @@ -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> { @@ -174,7 +175,7 @@ impl RenderContext { _ => {} } } - println!("{}", Attribute::Reset); + outln!("{}", Attribute::Reset); Ok(()) } pub fn flush(&mut self) -> Result<(), Box> { @@ -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(()); } } diff --git a/src/plugins/edit.rs b/src/plugins/edit.rs index fb0ac48ed..1b298258f 100644 --- a/src/plugins/edit.rs +++ b/src/plugins/edit.rs @@ -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>>; - struct Edit { - field: Option, + field: Option>, value: Option, } 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, .. } => { diff --git a/src/plugins/embed.rs b/src/plugins/embed.rs index 6dc539d10..8265af7ae 100644 --- a/src/plugins/embed.rs +++ b/src/plugins/embed.rs @@ -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()), + )) + } } } diff --git a/src/plugins/inc.rs b/src/plugins/inc.rs index b7b24025e..1bd8a3f0e 100644 --- a/src/plugins/inc.rs +++ b/src/plugins/inc.rs @@ -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>>; - struct Inc { - field: Option, + field: Option>, error: Option, action: Option, } @@ -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> = name + let fields: Vec = 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()) + ]) ); } diff --git a/src/plugins/insert.rs b/src/plugins/insert.rs index cfe3a27b5..674320f28 100644 --- a/src/plugins/insert.rs +++ b/src/plugins/insert.rs @@ -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>; - struct Insert { field: Option, - value: Option, + value: Option>, } impl Insert { fn new() -> Insert { @@ -20,38 +17,19 @@ impl Insert { fn insert(&self, value: Tagged) -> Result, 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()); } } diff --git a/src/plugins/str.rs b/src/plugins/str.rs index 5741897fd..d0caa94da 100644 --- a/src/plugins/str.rs +++ b/src/plugins/str.rs @@ -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>>; - struct Str { - field: Option, + field: Option>, params: Option>, error: Option, action: Option, @@ -62,7 +61,7 @@ impl Str { Ok(applied) } - fn for_field(&mut self, column_path: ColumnPath) { + fn for_field(&mut self, column_path: Tagged) { 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)| { - 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())), - ) + 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), + span_for_spanned_list(fields.iter().map(|p| p.span)), + ) + } + None => return error, } - None => { - return ShellError::labeled_error( - "Unknown column", - "row does not contain this column", - tag_for_tagged_list(fields.iter().map(|p| p.tag())), - ) - } - } - }), - ); + }), + ); - 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( - "column name", - value.tagged_type_name(), - )) - } + None => Err(ShellError::labeled_error( + "str could not find field to replace", + "column 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()) + ]) ) } diff --git a/src/plugins/textview.rs b/src/plugins/textview.rs index 88507183e..4c789b8d0 100644 --- a/src/plugins/textview.rs +++ b/src/plugins/textview.rs @@ -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, use_color_buffer } } - println!(""); + outln!(""); } fn scroll_view(s: &str) { diff --git a/src/prelude.rs b/src/prelude.rs index 1a87da286..160e105dd 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -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; diff --git a/src/shell/help_shell.rs b/src/shell/help_shell.rs index 88b4b3f34..7a6cd07ce 100644 --- a/src/shell/help_shell.rs +++ b/src/shell/help_shell.rs @@ -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(); } diff --git a/src/shell/helper.rs b/src/shell/helper.rs index 7e662a7dd..0df5e1d82 100644 --- a/src/shell/helper.rs +++ b/src/shell/helper.rs @@ -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 { diff --git a/src/shell/value_shell.rs b/src/shell/value_shell.rs index bc057e1e1..2b8e0843d 100644 --- a/src/shell/value_shell.rs +++ b/src/shell/value_shell.rs @@ -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(); } diff --git a/src/traits.rs b/src/traits.rs index a33453ab2..0b7d42411 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -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 ShellTypeName for &T { + fn type_name(&self) -> &'static str { + (*self).type_name() + } +} + +pub trait SpannedTypeName { + fn spanned_type_name(&self) -> Spanned<&'static str>; +} + +impl SpannedTypeName for Spanned { + fn spanned_type_name(&self) -> Spanned<&'static str> { + self.item.type_name().spanned(self.span) + } +} + +impl SpannedTypeName for Tagged { + 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 fmt::Display for Debuggable<'_, T> { +impl 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 fmt::Display for Debuggable<'_, T> { } } +impl 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>( + &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 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) -> 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, + separator: DebugDocBuilder, + ) -> DebugDocBuilder { + BoxAllocator.intersperse(list, separator).into() + } + + pub fn list(list: impl IntoIterator) -> 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 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 for PrettyDebugDoc { + fn from(input: DebugDoc) -> PrettyDebugDoc { + input.inner + } +} + +impl Into for DebugDocBuilder { + fn into(self) -> PrettyDebugDoc { + self.inner.into() + } +} + +fn hash_doc(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(&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 { diff --git a/src/utils.rs b/src/utils.rs index 66835ab83..ec4fd8362 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -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, -) -> Option> { - let field_tried = field_tried.as_string().unwrap(); +pub fn did_you_mean(obj_source: &Value, field_tried: &PathMember) -> Option> { + let field_tried = match &field_tried.item { + RawPathMember::String(string) => string.clone(), + RawPathMember::Int(int) => format!("{}", int), + }; let possibilities = obj_source.data_descriptors(); @@ -25,10 +26,10 @@ pub fn did_you_mean( if possible_matches.len() > 0 { possible_matches.sort(); - return Some(possible_matches); + Some(possible_matches) + } else { + None } - - None } pub struct AbsoluteFile { diff --git a/tests/command_get_tests.rs b/tests/command_get_tests.rs index 71fde763a..7dd51b10b 100644 --- a/tests/command_get_tests.rs +++ b/tests/command_get_tests.rs @@ -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]" + ); }) } diff --git a/tests/command_open_tests.rs b/tests/command_open_tests.rs index 48f438f3d..547690c76 100644 --- a/tests/command_open_tests.rs +++ b/tests/command_open_tests.rs @@ -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!( diff --git a/tests/commands_test.rs b/tests/commands_test.rs index 661d14023..2b52590bf 100644 --- a/tests/commands_test.rs +++ b/tests/commands_test.rs @@ -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!( diff --git a/tests/filters_test.rs b/tests/filters_test.rs index 311bf9317..7f38b6047 100644 --- a/tests/filters_test.rs +++ b/tests/filters_test.rs @@ -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 "# ));