From 69effbc9e7a46f446d7a63da7d37bc5782076795 Mon Sep 17 00:00:00 2001 From: Yehuda Katz Date: Fri, 31 May 2019 22:50:16 -0700 Subject: [PATCH] Improve signature infrastructure The `config` command uses different kinds of named arguments, which illustrates how it works. --- Cargo.lock | 77 +++++++++++++++++++++ Cargo.toml | 7 +- src/cli.rs | 1 + src/commands.rs | 2 + src/commands/cd.rs | 2 +- src/commands/classified.rs | 3 +- src/commands/column.rs | 4 +- src/commands/command.rs | 8 ++- src/commands/config.rs | 117 ++++++++++++++++++++++++++++++++ src/commands/open.rs | 6 +- src/commands/reject.rs | 4 +- src/commands/select.rs | 9 ++- src/commands/size.rs | 4 +- src/commands/skip.rs | 2 +- src/commands/sort_by.rs | 2 +- src/commands/split_column.rs | 2 +- src/commands/take.rs | 2 +- src/commands/view.rs | 2 +- src/commands/where_.rs | 4 +- src/context.rs | 7 +- src/env/host.rs | 4 +- src/errors.rs | 30 ++++++-- src/evaluate/evaluator.rs | 2 - src/format/table.rs | 4 ++ src/object.rs | 2 + src/object/base.rs | 88 ++++++++++++++++-------- src/object/config.rs | 60 ++++++++++++++++ src/object/desc.rs | 62 +++-------------- src/object/dict.rs | 30 ++++---- src/object/serialization.rs | 111 ++++++++++++++++++++++++++++++ src/object/types.rs | 33 ++------- src/parser.rs | 2 +- src/parser/ast.rs | 32 ++++----- src/parser/parser.lalrpop | 4 +- src/parser/parser.rs | 24 ++----- src/parser/registry.rs | 128 +++++++++++++++++++++++++++++++---- 36 files changed, 673 insertions(+), 208 deletions(-) create mode 100644 src/commands/config.rs create mode 100644 src/object/config.rs create mode 100644 src/object/serialization.rs diff --git a/Cargo.lock b/Cargo.lock index 810ae352b..ac7d54631 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -29,6 +29,17 @@ dependencies = [ "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "app_dirs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "ole32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "shell32-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "xdg 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "arc-swap" version = "0.3.11" @@ -169,6 +180,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "num-integer 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", "num-traits 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.91 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -832,6 +844,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" name = "indexmap" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "serde 1.0.91 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] name = "iovec" @@ -842,6 +857,11 @@ dependencies = [ "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "is-match" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "itertools" version = "0.7.11" @@ -1053,6 +1073,7 @@ name = "nu" version = "0.1.1" dependencies = [ "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "app_dirs 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "byte-unit 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", "chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1089,6 +1110,8 @@ dependencies = [ "sysinfo 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)", "term 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-fs 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "toml 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "toml-query 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1160,6 +1183,15 @@ name = "numtoa" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "ole32-sys" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "onig" version = "4.3.2" @@ -1648,6 +1680,15 @@ name = "shell-words" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "shell32-sys" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "signal-hook" version = "0.1.9" @@ -1903,6 +1944,30 @@ dependencies = [ "serde 1.0.91 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "toml-query" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "is-match 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "toml 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "toml-query_derive 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "toml-query_derive" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "darling 0.8.6 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.34 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "ucd-util" version = "0.1.3" @@ -2020,6 +2085,11 @@ dependencies = [ "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "xdg" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "xi-unicode" version = "0.1.0" @@ -2043,6 +2113,7 @@ dependencies = [ "checksum aho-corasick 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e6f484ae0c99fec2e858eb6134949117399f222608d84cadb3f58c1f97c2364c" "checksum ansi_colours 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1d0f302a81afc6a7f4350c04f0ba7cfab529cc009bca3324b3fb5764e6add8b6" "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +"checksum app_dirs 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e73a24bad9bd6a94d6395382a6c69fe071708ae4409f763c5475e14ee896313d" "checksum arc-swap 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)" = "bc4662175ead9cd84451d5c35070517777949a2ed84551764129cedb88384841" "checksum argon2rs 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3f67b0b6a86dae6e67ff4ca2b6201396074996379fba2b92ff649126f37cb392" "checksum array-macro 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7c4ff37a25fb442a1fecfd399be0dde685558bca30fb998420532889a36852d2" @@ -2135,6 +2206,7 @@ dependencies = [ "checksum ident_case 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" "checksum indexmap 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7e81a7c05f79578dbc15793d8b619db9ba32b4577003ef3af1a91c416798c58d" "checksum iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dbe6e417e7d0975db6512b90796e8ce223145ac4e33c377e4a42882a0e88bb08" +"checksum is-match 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7e5b386aef33a1c677be65237cb9d32c3f3ef56bd035949710c4bb13083eb053" "checksum itertools 0.7.11 (registry+https://github.com/rust-lang/crates.io-index)" = "0d47946d458e94a1b7bcabbf6521ea7c037062c81f534615abcad76e84d4970d" "checksum itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5b8467d9c1cebe26feb08c640139247fac215782d35371ade9a2136ed6085358" "checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" @@ -2169,6 +2241,7 @@ dependencies = [ "checksum num-traits 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "d9c79c952a4a139f44a0fe205c4ee66ce239c0e6ce72cd935f5f7e2f717549dd" "checksum num_cpus 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1a23f0ed30a54abaa0c7e83b1d2d87ada7c3c23078d1d87815af3e3b6385fbba" "checksum numtoa 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" +"checksum ole32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5d2c49021782e5233cd243168edfa8037574afed4eba4bbaf538b3d8d1789d8c" "checksum onig 4.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a646989adad8a19f49be2090374712931c3a59835cb5277b4530f48b417f26e7" "checksum onig_sys 69.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388410bf5fa341f10e58e6db3975f4bea1ac30247dd79d37a9e5ced3cb4cc3b0" "checksum ordered-float 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "18869315e81473c951eb56ad5558bbc56978562d3ecfb87abb7a1e944cea4518" @@ -2224,6 +2297,7 @@ dependencies = [ "checksum serde_derive 1.0.91 (registry+https://github.com/rust-lang/crates.io-index)" = "101b495b109a3e3ca8c4cbe44cf62391527cdfb6ba15821c5ce80bcd5ea23f9f" "checksum serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)" = "5a23aa71d4a4d43fdbfaac00eff68ba8a06a51759a89ac3304323e800c4dd40d" "checksum shell-words 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "39acde55a154c4cd3ae048ac78cc21c25f3a0145e44111b523279113dce0d94a" +"checksum shell32-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9ee04b46101f57121c9da2b151988283b6beb79b34f5bb29a58ee48cb695122c" "checksum signal-hook 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "72ab58f1fda436857e6337dcb6a5aaa34f16c5ddc87b3a8b6ef7a212f90b9c5a" "checksum signal-hook-registry 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cded4ffa32146722ec54ab1f16320568465aa922aa9ab4708129599740da85d7" "checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" @@ -2252,6 +2326,8 @@ dependencies = [ "checksum tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "5090db468dad16e1a7a54c8c67280c5e4b544f3d3e018f0b913b400261f85926" "checksum tokio-threadpool 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "72558af20be886ea124595ea0f806dd5703b8958e4705429dd58b3d8231f72f2" "checksum toml 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b8c96d7873fa7ef8bdeb3a9cda3ac48389b4154f32b9803b4bc26220b677b039" +"checksum toml-query 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a24369a1894ac8224efcfd567c3d141aea360292f49888e7ec7dcc316527aebb" +"checksum toml-query_derive 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3c99ca245ec273c7e75c8ee58f47b882d0146f3c2c8495158082c6671e8b5335" "checksum ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "535c204ee4d8434478593480b8f86ab45ec9aae0e83c568ca81abf0fd0e88f86" "checksum unicode-segmentation 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1967f4cdfc355b37fd76d2a954fb2ed3871034eb4f26d60537d88795cfc332a9" "checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526" @@ -2271,6 +2347,7 @@ dependencies = [ "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" "checksum wincolor 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "561ed901ae465d6185fa7864d63fbd5720d0ef718366c9a4dc83cf6170d7e9ba" "checksum winreg 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a27a759395c1195c4cc5cda607ef6f8f6498f64e78f7900f5de0a127a424704a" +"checksum xdg 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d089681aa106a86fade1b0128fb5daf07d5867a509ab036d99988dec80429a57" "checksum xi-unicode 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "12ea8eda4b1eb72f02d148402e23832d56a33f55d8c1b2d5bcdde91d79d47cb1" "checksum xml-rs 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "541b12c998c5b56aa2b4e6f18f03664eef9a4fd0a246a55594efae6cc2d964b5" "checksum yaml-rust 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "65923dd1784f44da1d2c3dbbc5e822045628c590ba72123e1c73d3c230c4434d" diff --git a/Cargo.toml b/Cargo.toml index 45afc34b3..0d0562ff9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ edition = "2018" [dependencies] rustyline = "4.1.0" sysinfo = "0.8.4" -chrono = "0.4.6" +chrono = { version = "0.4.6", features = ["serde"] } chrono-tz = "0.5.1" derive-new = "0.5.6" prettytable-rs = "0.8.0" @@ -21,7 +21,7 @@ conch-parser = "0.1.1" nom = "5.0.0-beta1" subprocess = "0.1.18" dunce = "1.0.0" -indexmap = "1.0.2" +indexmap = { version = "1.0.2", features = ["serde-1"] } chrono-humanize = "0.0.11" byte-unit = "2.1.0" ordered-float = "1.0.2" @@ -44,6 +44,9 @@ getset = "0.0.7" logos = "0.10.0-rc2" logos-derive = "0.10.0-rc2" language-reporting = "0.3.0" +app_dirs = "1.2.1" +toml = "0.5.1" +toml-query = "0.9.0" [dependencies.pancurses] version = "0.16" diff --git a/src/cli.rs b/src/cli.rs index a3785e8cb..59a5b6739 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -60,6 +60,7 @@ pub async fn cli() -> Result<(), Box> { command("to-array", to_array::to_array), command("to-json", to_json::to_json), Arc::new(Where), + Arc::new(Config), command("sort-by", sort_by::sort_by), ]); } diff --git a/src/commands.rs b/src/commands.rs index 23b79fad0..54484aed3 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -3,6 +3,7 @@ crate mod cd; crate mod classified; crate mod column; crate mod command; +crate mod config; crate mod from_json; crate mod ls; crate mod open; @@ -22,5 +23,6 @@ crate mod view; crate mod where_; crate use command::command; +crate use config::Config; crate use to_array::stream_to_array; crate use where_::Where; diff --git a/src/commands/cd.rs b/src/commands/cd.rs index ab2f59b31..1ebf8bef2 100644 --- a/src/commands/cd.rs +++ b/src/commands/cd.rs @@ -3,7 +3,7 @@ use crate::prelude::*; use std::env; pub fn cd(args: CommandArgs) -> Result { - let target = match args.args.first() { + let target = match args.positional.first() { // TODO: This needs better infra None => return Err(ShellError::string(format!("cd must take one arg"))), Some(v) => v.as_string()?.clone(), diff --git a/src/commands/classified.rs b/src/commands/classified.rs index 0f9c2d916..d744fd04e 100644 --- a/src/commands/classified.rs +++ b/src/commands/classified.rs @@ -1,3 +1,4 @@ +use crate::parser::registry::Args; use crate::prelude::*; use bytes::{BufMut, BytesMut}; use futures_codec::{Decoder, Encoder, Framed}; @@ -80,7 +81,7 @@ crate enum ClassifiedCommand { crate struct InternalCommand { crate command: Arc, - crate args: Vec, + crate args: Args, } impl InternalCommand { diff --git a/src/commands/column.rs b/src/commands/column.rs index 997f49a50..3e811771e 100644 --- a/src/commands/column.rs +++ b/src/commands/column.rs @@ -4,11 +4,11 @@ use crate::object::Value; use crate::prelude::*; pub fn column(args: CommandArgs) -> Result { - if args.args.is_empty() { + if args.positional.is_empty() { return Err(ShellError::string("select requires a field")); } - let fields: Result, _> = args.args.iter().map(|a| a.as_string()).collect(); + let fields: Result, _> = args.positional.iter().map(|a| a.as_string()).collect(); let fields = fields?; let objects = args diff --git a/src/commands/command.rs b/src/commands/command.rs index a9d1074d5..8ac5ee061 100644 --- a/src/commands/command.rs +++ b/src/commands/command.rs @@ -7,20 +7,22 @@ use std::path::PathBuf; pub struct CommandArgs { pub host: Arc>, pub env: Arc>, - pub args: Vec, + pub positional: Vec, + pub named: indexmap::IndexMap, pub input: InputStream, } impl CommandArgs { crate fn from_context( ctx: &'caller mut Context, - args: Vec, + positional: Vec, input: InputStream, ) -> CommandArgs { CommandArgs { host: ctx.host.clone(), env: ctx.env.clone(), - args, + positional, + named: indexmap::IndexMap::default(), input, } } diff --git a/src/commands/config.rs b/src/commands/config.rs new file mode 100644 index 000000000..11b35a84e --- /dev/null +++ b/src/commands/config.rs @@ -0,0 +1,117 @@ +use crate::errors::ShellError; +use crate::object::config; +use crate::object::Value; +use crate::parser::registry::{NamedType, NamedValue}; +use crate::parser::CommandConfig; +use crate::prelude::*; +use indexmap::IndexMap; +use log::trace; + +pub struct Config; + +impl Command for Config { + fn run(&self, args: CommandArgs) -> Result { + config(args) + } + fn name(&self) -> &str { + "config" + } + + fn config(&self) -> CommandConfig { + let mut named: IndexMap = IndexMap::new(); + named.insert("set".to_string(), NamedType::Optional(NamedValue::Tuple)); + named.insert("get".to_string(), NamedType::Optional(NamedValue::Single)); + named.insert("clear".to_string(), NamedType::Switch); + + named.insert( + "remove".to_string(), + NamedType::Optional(NamedValue::Single), + ); + + CommandConfig { + name: self.name().to_string(), + mandatory_positional: vec![], + optional_positional: vec![], + rest_positional: false, + named, + } + } +} + +pub fn config(args: CommandArgs) -> Result { + let mut result = crate::object::config::config()?; + + trace!("{:#?}", args.positional); + trace!("{:#?}", args.named); + + if let Some(v) = args.named.get("get") { + let key = v.as_string()?; + let value = result + .get(&key) + .ok_or_else(|| ShellError::string(&format!("Missing key {} in config", key)))?; + + return Ok( + futures::stream::once(futures::future::ready(ReturnValue::Value(value.clone()))) + .boxed(), + ); + } + + if let Some(v) = args.named.get("set") { + if let Ok((key, value)) = v.as_pair() { + result.insert(key.as_string()?, value.clone()); + + config::write_config(&result)?; + + return Ok( + futures::stream::once(futures::future::ready(ReturnValue::Value(Value::Object( + result.into(), + )))) + .boxed(), + ); + } + } + + if let Some(_) = args.named.get("clear") { + result.clear(); + + config::write_config(&result)?; + + return Ok( + futures::stream::once(futures::future::ready(ReturnValue::Value(Value::Object( + result.into(), + )))) + .boxed(), + ); + } + + if let Some(v) = args.named.get("remove") { + let key = v.as_string()?; + + if result.contains_key(&key) { + result.remove(&key); + } else { + return Err(ShellError::string(&format!( + "{} does not exist in config", + key + ))); + } + + return Ok( + futures::stream::once(futures::future::ready(ReturnValue::Value(Value::Object( + result.into(), + )))) + .boxed(), + ); + } + + if args.positional.len() == 0 { + return Ok( + futures::stream::once(futures::future::ready(ReturnValue::Value(Value::Object( + result.into(), + )))) + .boxed(), + ); + } + + Err(ShellError::string(format!("Unimplemented"))) +} diff --git a/src/commands/open.rs b/src/commands/open.rs index ec2b4905d..cce5b7664 100644 --- a/src/commands/open.rs +++ b/src/commands/open.rs @@ -6,7 +6,7 @@ use std::path::PathBuf; pub fn open(args: CommandArgs) -> Result { let cwd = args.env.lock().unwrap().cwd().to_path_buf(); let mut full_path = PathBuf::from(cwd); - match &args.args[0] { + match &args.positional[0] { Value::Primitive(Primitive::String(s)) => full_path.push(s), _ => {} } @@ -14,7 +14,9 @@ pub fn open(args: CommandArgs) -> Result { let contents = std::fs::read_to_string(&full_path).unwrap(); let mut stream = VecDeque::new(); - stream.push_back(ReturnValue::Value(Value::Primitive(Primitive::String(contents)))); + stream.push_back(ReturnValue::Value(Value::Primitive(Primitive::String( + contents, + )))); Ok(stream.boxed()) } diff --git a/src/commands/reject.rs b/src/commands/reject.rs index ef8313ff7..be447eb6c 100644 --- a/src/commands/reject.rs +++ b/src/commands/reject.rs @@ -4,11 +4,11 @@ use crate::object::Value; use crate::prelude::*; pub fn reject(args: CommandArgs) -> Result { - if args.args.is_empty() { + if args.positional.is_empty() { return Err(ShellError::string("select requires a field")); } - let fields: Result, _> = args.args.iter().map(|a| a.as_string()).collect(); + let fields: Result, _> = args.positional.iter().map(|a| a.as_string()).collect(); let fields = fields?; let stream = args diff --git a/src/commands/select.rs b/src/commands/select.rs index 34020dca1..c64d7548e 100644 --- a/src/commands/select.rs +++ b/src/commands/select.rs @@ -8,7 +8,10 @@ fn get_member(path: &str, obj: &Value) -> Option { match current.get_data_by_key(p) { Some(v) => current = v, None => { - return Some(Value::Error(Box::new(ShellError::string(format!("Object field name not found: {}", p))))) + return Some(Value::Error(Box::new(ShellError::string(format!( + "Object field name not found: {}", + p + ))))) } } } @@ -17,11 +20,11 @@ fn get_member(path: &str, obj: &Value) -> Option { } pub fn select(args: CommandArgs) -> Result { - if args.args.is_empty() { + if args.positional.is_empty() { return Err(ShellError::string("select requires a field")); } - let fields: Result, _> = args.args.iter().map(|a| a.as_string()).collect(); + let fields: Result, _> = args.positional.iter().map(|a| a.as_string()).collect(); let fields = fields?; let stream = args diff --git a/src/commands/size.rs b/src/commands/size.rs index 3b0858a77..baf3c3303 100644 --- a/src/commands/size.rs +++ b/src/commands/size.rs @@ -6,7 +6,7 @@ use std::fs::File; use std::io::prelude::*; pub fn size(args: CommandArgs) -> Result { - if args.args.is_empty() { + if args.positional.is_empty() { return Err(ShellError::string("size requires at least one file")); } let cwd = args.env.lock().unwrap().cwd().to_path_buf(); @@ -14,7 +14,7 @@ pub fn size(args: CommandArgs) -> Result { let mut contents = String::new(); let mut list = VecDeque::new(); - for name in args.args { + for name in args.positional { let name = name.as_string()?; let path = cwd.join(&name); let mut file = File::open(path)?; diff --git a/src/commands/skip.rs b/src/commands/skip.rs index 867a53612..83309e4d8 100644 --- a/src/commands/skip.rs +++ b/src/commands/skip.rs @@ -2,7 +2,7 @@ use crate::errors::ShellError; use crate::prelude::*; pub fn skip(args: CommandArgs) -> Result { - let amount = args.args[0].as_i64()?; + let amount = args.positional[0].as_i64()?; let input = args.input; diff --git a/src/commands/sort_by.rs b/src/commands/sort_by.rs index 78e3d01d7..d488d5cbd 100644 --- a/src/commands/sort_by.rs +++ b/src/commands/sort_by.rs @@ -2,7 +2,7 @@ use crate::errors::ShellError; use crate::prelude::*; pub fn sort_by(args: CommandArgs) -> Result { - let fields: Result, _> = args.args.iter().map(|a| a.as_string()).collect(); + let fields: Result, _> = args.positional.iter().map(|a| a.as_string()).collect(); let fields = fields?; let output = args.input.collect::>(); diff --git a/src/commands/split_column.rs b/src/commands/split_column.rs index 9a878d189..81b548080 100644 --- a/src/commands/split_column.rs +++ b/src/commands/split_column.rs @@ -7,7 +7,7 @@ use log::debug; pub fn split_column(args: CommandArgs) -> Result { let input = args.input; - let args = args.args; + let args = args.positional; Ok(input .map(move |v| match v { diff --git a/src/commands/take.rs b/src/commands/take.rs index 2ad1c70f9..03ac49226 100644 --- a/src/commands/take.rs +++ b/src/commands/take.rs @@ -4,7 +4,7 @@ use crate::prelude::*; // TODO: "Amount remaining" wrapper pub fn take(args: CommandArgs) -> Result { - let amount = args.args[0].as_i64()?; + let amount = args.positional[0].as_i64()?; let input = args.input; diff --git a/src/commands/view.rs b/src/commands/view.rs index 0c6b5526a..3125cd6e6 100644 --- a/src/commands/view.rs +++ b/src/commands/view.rs @@ -3,7 +3,7 @@ use crate::prelude::*; use prettyprint::PrettyPrinter; pub fn view(args: CommandArgs) -> Result { - let target = match args.args.first() { + let target = match args.positional.first() { // TODO: This needs better infra None => return Err(ShellError::string(format!("cat must take one arg"))), Some(v) => v.as_string()?.clone(), diff --git a/src/commands/where_.rs b/src/commands/where_.rs index 8f1f66016..45ac0c7fe 100644 --- a/src/commands/where_.rs +++ b/src/commands/where_.rs @@ -25,11 +25,11 @@ impl Command for Where { } pub fn r#where(args: CommandArgs) -> Result { - if args.args.is_empty() { + if args.positional.is_empty() { return Err(ShellError::string("select requires a field")); } - let block = args.args[0].as_block()?; + let block = args.positional[0].as_block()?; let input = args.input; let objects = input.filter_map(move |item| { diff --git a/src/context.rs b/src/context.rs index f22144afc..5b75f6814 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1,4 +1,4 @@ -use crate::parser::{CommandConfig, CommandRegistry}; +use crate::parser::{Args, CommandConfig, CommandRegistry}; use crate::prelude::*; use indexmap::IndexMap; @@ -47,13 +47,14 @@ impl Context { crate fn run_command( &mut self, command: Arc, - arg_list: Vec, + args: Args, input: InputStream, ) -> Result { let command_args = CommandArgs { host: self.host.clone(), env: self.env.clone(), - args: arg_list, + positional: args.positional, + named: args.named, input, }; diff --git a/src/env/host.rs b/src/env/host.rs index edf9e5430..787b72856 100644 --- a/src/env/host.rs +++ b/src/env/host.rs @@ -1,7 +1,8 @@ use crate::prelude::*; use language_reporting::termcolor; +use std::fmt::Debug; -pub trait Host { +pub trait Host: Debug { fn out_terminal(&self) -> Box; fn err_terminal(&self) -> Box; @@ -38,6 +39,7 @@ impl Host for Box { } } +#[derive(Debug)] crate struct BasicHost; impl Host for BasicHost { diff --git a/src/errors.rs b/src/errors.rs index 5fbd99ebb..280be9b18 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -3,10 +3,10 @@ use crate::parser::lexer::{Span, SpannedToken}; use crate::prelude::*; use derive_new::new; use language_reporting::Diagnostic; -use serde::{Serialize, Serializer}; -use serde_derive::Serialize; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use serde_derive::{Deserialize, Serialize}; -#[derive(Debug, Eq, PartialEq, Clone, Ord, PartialOrd, Serialize)] +#[derive(Debug, Eq, PartialEq, Clone, Ord, PartialOrd, Serialize, Deserialize)] pub enum ShellError { String(StringError), Diagnostic(ShellDiagnostic, String), @@ -85,7 +85,20 @@ impl Serialize for ShellDiagnostic { } } -#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, new, Clone, Serialize)] +impl Deserialize<'de> for ShellDiagnostic { + fn deserialize(_deserializer: D) -> Result + where + D: Deserializer<'de>, + { + Ok(ShellDiagnostic { + diagnostic: Diagnostic::new( + language_reporting::Severity::Error, + "deserialize not implemented for ShellDiagnostic", + ), + }) + } +} +#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, new, Clone, Serialize, Deserialize)] pub struct StringError { title: String, error: Value, @@ -137,3 +150,12 @@ impl std::convert::From> for ShellError }) } } + +impl std::convert::From for ShellError { + fn from(input: toml::ser::Error) -> ShellError { + ShellError::String(StringError { + title: format!("{:?}", input), + error: Value::nothing(), + }) + } +} diff --git a/src/evaluate/evaluator.rs b/src/evaluate/evaluator.rs index 5b314e87e..71eab8b13 100644 --- a/src/evaluate/evaluator.rs +++ b/src/evaluate/evaluator.rs @@ -47,8 +47,6 @@ fn evaluate_reference(r: &ast::Variable, scope: &Scope) -> Result Ok(scope.it.copy()), - True => Ok(Value::boolean(true)), - False => Ok(Value::boolean(false)), Other(s) => Err(ShellError::string(&format!( "Unimplemented variable reference: {}", s diff --git a/src/format/table.rs b/src/format/table.rs index a7436a87f..29a254f91 100644 --- a/src/format/table.rs +++ b/src/format/table.rs @@ -24,6 +24,10 @@ impl TableView { let item = &values[0]; let descs = item.data_descriptors(); + if descs.len() == 0 { + return None; + } + let headers: Vec = descs.iter().map(|d| d.name.display().to_string()).collect(); let mut entries = vec![]; diff --git a/src/object.rs b/src/object.rs index 1b6557d54..21601156e 100644 --- a/src/object.rs +++ b/src/object.rs @@ -1,8 +1,10 @@ crate mod base; +crate mod config; crate mod desc; crate mod dict; crate mod files; crate mod process; +crate mod serialization; crate mod types; crate use base::{Primitive, Value}; diff --git a/src/object/base.rs b/src/object/base.rs index e938c5460..629699da2 100644 --- a/src/object/base.rs +++ b/src/object/base.rs @@ -10,12 +10,27 @@ use derive_new::new; use ordered_float::OrderedFloat; use std::time::SystemTime; -use serde::{Serialize, Serializer}; -use serde_derive::Serialize; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use serde_derive::{Deserialize, Serialize}; -type OF64 = OrderedFloat; +#[derive(Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq, new)] +pub struct OF64 { + crate inner: OrderedFloat, +} -#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq)] +impl OF64 { + crate fn into_inner(&self) -> f64 { + self.inner.into_inner() + } +} + +impl From for OF64 { + fn from(float: f64) -> Self { + OF64::new(OrderedFloat(float)) + } +} + +#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Deserialize)] pub enum Primitive { Nothing, Int(i64), @@ -35,7 +50,7 @@ impl Serialize for Primitive { match self { Primitive::Nothing => serializer.serialize_i32(0), Primitive::Int(i) => serializer.serialize_i64(*i), - Primitive::Float(f) => serializer.serialize_f64(f.into_inner()), + Primitive::Float(OF64 { inner: f }) => serializer.serialize_f64(f.into_inner()), Primitive::Bytes(b) => serializer.serialize_u128(*b), Primitive::String(ref s) => serializer.serialize_str(s), Primitive::Boolean(b) => serializer.serialize_bool(*b), @@ -63,7 +78,7 @@ impl Primitive { } } Primitive::Int(i) => format!("{}", i), - Primitive::Float(f) => format!("{:.*}", 2, f.into_inner()), + Primitive::Float(OF64 { inner: f }) => format!("{:.*}", 2, f.into_inner()), Primitive::String(s) => format!("{}", s), Primitive::Boolean(b) => match (b, field_name) { (true, None) => format!("Yes"), @@ -97,6 +112,17 @@ impl Serialize for Block { } } +impl Deserialize<'de> for Block { + fn deserialize(_deserializer: D) -> Result + where + D: Deserializer<'de>, + { + Ok(Block::new(ast::Expression::Leaf(ast::Leaf::String( + format!("Unserializable block"), + )))) + } +} + impl Block { pub fn invoke(&self, value: &Value) -> Result { let scope = Scope::new(value.copy()); @@ -115,21 +141,6 @@ pub enum Value { Error(Box), } -impl Serialize for Value { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - match self { - Value::Primitive(p) => p.serialize(serializer), - Value::Object(o) => o.serialize(serializer), - Value::List(l) => l.serialize(serializer), - Value::Block(b) => b.serialize(serializer), - Value::Error(e) => e.serialize(serializer), - } - } -} - impl Value { crate fn data_descriptors(&self) -> Vec { match self { @@ -207,6 +218,24 @@ impl Value { } } + #[allow(unused)] + crate fn is_string(&self, expected: &str) -> bool { + match self { + Value::Primitive(Primitive::String(s)) if s == expected => true, + other => false, + } + } + + crate fn as_pair(&self) -> Result<(Value, Value), ShellError> { + match self { + Value::List(list) if list.len() == 2 => Ok((list[0].clone(), list[1].clone())), + other => Err(ShellError::string(format!( + "Expected pair, got {:?}", + other + ))), + } + } + crate fn as_string(&self) -> Result { match self { Value::Primitive(Primitive::String(s)) => Ok(s.to_string()), @@ -280,8 +309,7 @@ impl Value { Value::Primitive(Primitive::Float(s.into())) } - #[allow(unused)] - crate fn bool(s: impl Into) -> Value { + crate fn boolean(s: impl Into) -> Value { Value::Primitive(Primitive::Boolean(s.into())) } @@ -289,6 +317,16 @@ impl Value { Value::Primitive(Primitive::Date(s.into())) } + #[allow(unused)] + crate fn date_from_str(s: &str) -> Result { + let date = DateTime::parse_from_rfc3339(s) + .map_err(|err| ShellError::string(&format!("Date parse error: {}", err)))?; + + let date = date.with_timezone(&chrono::offset::Utc); + + Ok(Value::Primitive(Primitive::Date(date))) + } + #[allow(unused)] crate fn system_date_result(s: Result) -> Value { match s { @@ -297,10 +335,6 @@ impl Value { } } - crate fn boolean(s: impl Into) -> Value { - Value::Primitive(Primitive::Boolean(s.into())) - } - crate fn nothing() -> Value { Value::Primitive(Primitive::Nothing) } diff --git a/src/object/config.rs b/src/object/config.rs new file mode 100644 index 000000000..288ed2132 --- /dev/null +++ b/src/object/config.rs @@ -0,0 +1,60 @@ +use crate::errors::ShellError; +use crate::prelude::*; +use app_dirs::*; +use indexmap::IndexMap; +use log::trace; +use serde_derive::{Deserialize, Serialize}; +use std::fs::{self, OpenOptions}; +use std::io; +use std::path::Path; + +const APP_INFO: AppInfo = AppInfo { + name: "nu", + author: "nu shell developers", +}; + +#[derive(Deserialize, Serialize)] +struct Config { + #[serde(flatten)] + extra: IndexMap, +} + +crate fn write_config(map: &IndexMap) -> Result<(), ShellError> { + let location = app_root(AppDataType::UserConfig, &APP_INFO) + .map_err(|err| ShellError::string(&format!("Couldn't open config file:\n{}", err)))?; + + let filename = location.join("config.toml"); + touch(&filename)?; + + let contents = toml::to_string(&Config { extra: map.clone() })?; + + fs::write(&filename, &contents)?; + + Ok(()) +} + +crate fn config() -> Result, ShellError> { + let location = app_root(AppDataType::UserConfig, &APP_INFO) + .map_err(|err| ShellError::string(&format!("Couldn't open config file:\n{}", err)))?; + + let filename = location.join("config.toml"); + touch(&filename)?; + + trace!("config file = {}", filename.display()); + + let contents = fs::read_to_string(filename) + .map_err(|err| ShellError::string(&format!("Couldn't read config file:\n{}", err)))?; + + let parsed: Config = toml::from_str(&contents) + .map_err(|err| ShellError::string(&format!("Couldn't parse config file:\n{}", err)))?; + + Ok(parsed.extra) +} + +// A simple implementation of `% touch path` (ignores existing files) +fn touch(path: &Path) -> io::Result<()> { + match OpenOptions::new().create(true).write(true).open(path) { + Ok(_) => Ok(()), + Err(e) => Err(e), + } +} diff --git a/src/object/desc.rs b/src/object/desc.rs index bf54bd44e..476826238 100644 --- a/src/object/desc.rs +++ b/src/object/desc.rs @@ -1,8 +1,8 @@ -use crate::object::types::{AnyShell, Type}; +use crate::object::types::Type; use derive_new::new; -use serde::{Serialize, Serializer}; +use serde_derive::{Deserialize, Serialize}; -#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize, Hash)] pub enum DescriptorName { String(String), ValueOf, @@ -31,23 +31,11 @@ impl DescriptorName { } } -#[derive(Debug, new)] +#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq, Hash, new)] pub struct DataDescriptor { crate name: DescriptorName, crate readonly: bool, - crate ty: Box, -} - -impl Serialize for DataDescriptor { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - match self.name { - DescriptorName::String(ref s) => serializer.serialize_str(s), - DescriptorName::ValueOf => serializer.serialize_str("value_of") - } - } + crate ty: Type, } impl From<&str> for DataDescriptor { @@ -55,7 +43,7 @@ impl From<&str> for DataDescriptor { DataDescriptor { name: DescriptorName::String(input.to_string()), readonly: true, - ty: Box::new(AnyShell), + ty: Type::Any, } } } @@ -65,49 +53,23 @@ impl From for DataDescriptor { DataDescriptor { name: DescriptorName::String(input), readonly: true, - ty: Box::new(AnyShell), + ty: Type::Any, } } } -impl PartialEq for DataDescriptor { - fn eq(&self, other: &DataDescriptor) -> bool { - self.name == other.name && self.readonly == other.readonly && self.ty.equal(&*other.ty) - } -} - -impl std::hash::Hash for DataDescriptor { - fn hash(&self, state: &mut H) { - self.name.hash(state); - self.readonly.hash(state); - self.ty.id().hash(state); - } -} - -impl Eq for DataDescriptor {} - impl DescriptorName { crate fn for_string_name(name: impl Into) -> DescriptorName { DescriptorName::String(name.into()) } } -impl Clone for DataDescriptor { - fn clone(&self) -> DataDescriptor { - DataDescriptor { - name: self.name.clone(), - readonly: self.readonly, - ty: self.ty.copy(), - } - } -} - impl DataDescriptor { crate fn value_of() -> DataDescriptor { DataDescriptor { name: DescriptorName::ValueOf, readonly: true, - ty: Box::new(AnyShell), + ty: Type::Any, } } @@ -115,7 +77,7 @@ impl DataDescriptor { DataDescriptor { name: name.into(), readonly: true, - ty: Box::new(AnyShell), + ty: Type::Any, } } @@ -124,10 +86,6 @@ impl DataDescriptor { } crate fn copy(&self) -> DataDescriptor { - DataDescriptor { - name: self.name.clone(), - readonly: self.readonly, - ty: self.ty.copy(), - } + self.clone() } } diff --git a/src/object/dict.rs b/src/object/dict.rs index 64951fe2b..36216564f 100644 --- a/src/object/dict.rs +++ b/src/object/dict.rs @@ -2,28 +2,16 @@ use crate::prelude::*; use crate::object::DataDescriptor; use crate::object::{Primitive, Value}; +use derive_new::new; use indexmap::IndexMap; -use serde::ser::{Serialize, Serializer, SerializeMap}; +use serde_derive::{Deserialize, Serialize}; use std::cmp::{Ordering, PartialOrd}; -#[derive(Debug, Default, Eq, PartialEq, Clone)] +#[derive(Debug, Default, Eq, PartialEq, Serialize, Deserialize, Clone, new)] pub struct Dictionary { entries: IndexMap, } -impl Serialize for Dictionary { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let mut map = serializer.serialize_map(Some(self.entries.len()))?; - for (k, v) in &self.entries { - map.serialize_entry(&k, &v)?; - } - map.end() - } -} - impl PartialOrd for Dictionary { // TODO: FIXME fn partial_cmp(&self, _other: &Dictionary) -> Option { @@ -31,6 +19,18 @@ impl PartialOrd for Dictionary { } } +impl From> for Dictionary { + fn from(input: IndexMap) -> Dictionary { + let mut out = IndexMap::default(); + + for (key, value) in input { + out.insert(DataDescriptor::for_string_name(key), value); + } + + Dictionary::new(out) + } +} + impl Ord for Dictionary { // TODO: FIXME fn cmp(&self, _other: &Dictionary) -> Ordering { diff --git a/src/object/serialization.rs b/src/object/serialization.rs new file mode 100644 index 000000000..99ff83ea2 --- /dev/null +++ b/src/object/serialization.rs @@ -0,0 +1,111 @@ +use crate::object::base::OF64; +use crate::prelude::*; + +use ordered_float::OrderedFloat; +use serde::de::{self, Visitor}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use std::fmt; + +struct OF64Visitor; + +impl Visitor<'_> for OF64Visitor { + type Value = OF64; + + fn visit_f64(self, value: f64) -> Result { + Ok(OF64::new(OrderedFloat(value))) + } + + fn visit_f32(self, value: f32) -> Result { + Ok(OF64::new(OrderedFloat(value as f64))) + } + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a float") + } +} + +impl<'de> Deserialize<'de> for OF64 { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_f64(OF64Visitor) + } +} + +impl Serialize for Value { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match self { + Value::Primitive(p) => p.serialize(serializer), + Value::Object(o) => o.serialize(serializer), + Value::List(l) => l.serialize(serializer), + Value::Block(b) => b.serialize(serializer), + Value::Error(e) => e.serialize(serializer), + } + } +} + +struct ValueVisitor; + +impl<'de> Visitor<'de> for ValueVisitor { + type Value = Value; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a shell value") + } + + fn visit_i32(self, value: i32) -> Result + where + E: de::Error, + { + Ok(Value::int(value)) + } + + fn visit_i64(self, value: i64) -> Result + where + E: de::Error, + { + Ok(Value::int(value)) + } + + fn visit_u32(self, value: u32) -> Result + where + E: de::Error, + { + Ok(Value::int(value)) + } + + fn visit_u64(self, value: u64) -> Result + where + E: de::Error, + { + // TODO: Handle overflow better + Ok(Value::int(value as i64)) + } + + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + Ok(Value::string(value)) + } + + fn visit_bool(self, value: bool) -> Result + where + E: de::Error, + { + Ok(Value::boolean(value)) + } +} + +impl<'de> Deserialize<'de> for Value { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_any(ValueVisitor) + } +} diff --git a/src/object/types.rs b/src/object/types.rs index 4edd0203a..2f1bc2e4e 100644 --- a/src/object/types.rs +++ b/src/object/types.rs @@ -1,30 +1,7 @@ -use std::any::Any; -use std::fmt::Debug; +use derive_new::new; +use serde_derive::{Deserialize, Serialize}; -pub trait Type: Debug + Send { - fn as_any(&self) -> &dyn Any; - fn equal(&self, other: &dyn Type) -> bool; - fn id(&self) -> u64; - fn copy(&self) -> Box; -} - -#[derive(Debug, Eq, PartialEq)] -pub struct AnyShell; - -impl Type for AnyShell { - fn as_any(&self) -> &dyn Any { - self as &dyn Any - } - - fn equal(&self, other: &dyn Type) -> bool { - other.as_any().is::() - } - - fn id(&self) -> u64 { - 0 - } - - fn copy(&self) -> Box { - Box::new(AnyShell) - } +#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq, Hash, new)] +pub enum Type { + Any, } diff --git a/src/parser.rs b/src/parser.rs index a009cb0d7..742e6ca77 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -6,7 +6,7 @@ crate mod registry; crate mod span; crate use ast::{ParsedCommand, Pipeline}; -crate use registry::{CommandConfig, CommandRegistry}; +crate use registry::{Args, CommandConfig, CommandRegistry}; use crate::errors::ShellError; use lexer::Lexer; diff --git a/src/parser/ast.rs b/src/parser/ast.rs index a17a4536f..50abd228f 100644 --- a/src/parser/ast.rs +++ b/src/parser/ast.rs @@ -85,6 +85,13 @@ impl Expression { _ => None, } } + + crate fn is_flag(&self, value: &str) -> bool { + match self { + Expression::Flag(Flag::Longhand(f)) if value == f => true, + _ => false, + } + } } #[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, new)] @@ -151,17 +158,22 @@ impl Path { #[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq)] pub enum Variable { It, - True, - False, Other(String), } impl Variable { + crate fn from_str(input: &str) -> Expression { + match input { + "it" => Expression::VariableReference(Variable::It), + "true" => Expression::Leaf(Leaf::Boolean(true)), + "false" => Expression::Leaf(Leaf::Boolean(false)), + other => Expression::VariableReference(Variable::Other(other.to_string())), + } + } + fn print(&self) -> String { match self { Variable::It => format!("$it"), - Variable::True => format!("$true"), - Variable::False => format!("$false"), Variable::Other(s) => format!("${}", s), } } @@ -171,18 +183,6 @@ impl Variable { } } -impl FromStr for Variable { - type Err = (); - fn from_str(input: &str) -> Result::Err> { - Ok(match input { - "it" => Variable::It, - "true" => Variable::True, - "false" => Variable::False, - other => Variable::Other(other.to_string()), - }) - } -} - #[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq)] pub struct BarePath { head: String, diff --git a/src/parser/parser.lalrpop b/src/parser/parser.lalrpop index 2d2de686a..16d1b5ab6 100644 --- a/src/parser/parser.lalrpop +++ b/src/parser/parser.lalrpop @@ -65,8 +65,8 @@ Expr: Expression = { } -Variable: Variable = { - "$" <"variable"> => Variable::from_str(<>.as_slice()).unwrap(), +Variable: Expression = { + "$" <"variable"> => Variable::from_str(<>.as_slice()), } Member: String = { diff --git a/src/parser/parser.rs b/src/parser/parser.rs index 1b11bae89..a387a1528 100644 --- a/src/parser/parser.rs +++ b/src/parser/parser.rs @@ -1,5 +1,5 @@ // auto-generated: "lalrpop 0.17.0" -// sha256: efaf89a1d956869b47a3f5daff048341c19934c68ad5e1ed9fe8e5c4222d2 +// sha256: 327a2eaaded6615e365add5d44719ae0dd3217f5b0fc3ba130f052328c2bd439 #![allow(unused)] use std::str::FromStr; use crate::parser::ast::*; @@ -41,7 +41,6 @@ mod __parse__Pipeline { Variant10(i64), Variant11(Operator), Variant12(Pipeline), - Variant13(Variable), } const __ACTION: &'static [i8] = &[ // State 0 @@ -1364,17 +1363,6 @@ mod __parse__Pipeline { _ => panic!("symbol type mismatch") } } - fn __pop_Variant13< - 'input, - >( - __symbols: &mut ::std::vec::Vec<(usize,__Symbol<'input>,usize)> - ) -> (usize, Variable, usize) - { - match __symbols.pop().unwrap() { - (__l, __Symbol::Variant13(__v), __r) => (__l, __v, __r), - _ => panic!("symbol type mismatch") - } - } fn __pop_Variant10< 'input, >( @@ -2022,7 +2010,7 @@ mod __parse__Pipeline { ) -> (usize, usize) { // Leaf = Variable => ActionFn(9); - let __sym0 = __pop_Variant13(__symbols); + let __sym0 = __pop_Variant6(__symbols); let __start = __sym0.0.clone(); let __end = __sym0.2.clone(); let __nt = super::__action9::<>(__sym0); @@ -2393,7 +2381,7 @@ mod __parse__Pipeline { let __start = __sym0.0.clone(); let __end = __sym1.2.clone(); let __nt = super::__action25::<>(__sym0, __sym1); - __symbols.push((__start, __Symbol::Variant13(__nt), __end)); + __symbols.push((__start, __Symbol::Variant6(__nt), __end)); (2, 25) } pub(crate) fn __reduce52< @@ -2522,7 +2510,7 @@ fn __action8< fn __action9< 'input, >( - (_, __0, _): (usize, Variable, usize), + (_, __0, _): (usize, Expression, usize), ) -> Expression { Expression::VariableReference(__0) @@ -2679,9 +2667,9 @@ fn __action25< >( (_, _, _): (usize, SpannedToken<'input>, usize), (_, __0, _): (usize, SpannedToken<'input>, usize), -) -> Variable +) -> Expression { - Variable::from_str(__0.as_slice()).unwrap() + Variable::from_str(__0.as_slice()) } fn __action26< diff --git a/src/parser/registry.rs b/src/parser/registry.rs index d0e3dfdc1..a58918950 100644 --- a/src/parser/registry.rs +++ b/src/parser/registry.rs @@ -5,10 +5,21 @@ use indexmap::IndexMap; #[allow(unused)] #[derive(Debug)] pub enum NamedType { - Switch(String), - Single(String), - Array(String), - Block(String), + Switch, + Mandatory(NamedValue), + Optional(NamedValue), +} + +#[derive(Debug)] +pub enum NamedValue { + Single, + Tuple, + + #[allow(unused)] + Block, + + #[allow(unused)] + Array, } #[allow(unused)] @@ -62,13 +73,55 @@ pub struct CommandConfig { crate named: IndexMap, } +pub struct Args { + pub positional: Vec, + pub named: IndexMap, +} + impl CommandConfig { crate fn evaluate_args( &self, - mut args: impl Iterator, + args: impl Iterator, scope: &Scope, - ) -> Result, ShellError> { - let mut results: Vec = vec![]; + ) -> Result { + let mut positional: Vec = vec![]; + let mut named: IndexMap = IndexMap::default(); + + let mut args: Vec = args.cloned().collect(); + + for (key, ty) in self.named.iter() { + let index = args.iter().position(|a| a.is_flag(&key)); + + match (index, ty) { + (Some(i), NamedType::Switch) => { + args.remove(i); + named.insert(key.clone(), Value::boolean(true)); + } + + (None, NamedType::Switch) => {} + + (Some(i), NamedType::Optional(v)) => { + args.remove(i); + named.insert(key.clone(), extract_named(&mut args, i, v)?); + } + + (None, NamedType::Optional(_)) => {} + + (Some(i), NamedType::Mandatory(v)) => { + args.remove(i); + named.insert(key.clone(), extract_named(&mut args, i, v)?); + } + + (None, NamedType::Mandatory(_)) => { + return Err(ShellError::string(&format!( + "Expected mandatory argument {}, but it was missing", + key + ))) + } + } + } + + let mut args = args.into_iter(); for param in &self.mandatory_positional { let arg = args.next(); @@ -84,21 +137,25 @@ impl CommandConfig { Some(arg) => param.evaluate(arg.clone(), scope)?, }; - results.push(value); + positional.push(value); } if self.rest_positional { let rest: Result, _> = - args.map(|i| evaluate_expr(i, &Scope::empty())).collect(); - results.extend(rest?); + args.map(|i| evaluate_expr(&i, &Scope::empty())).collect(); + positional.extend(rest?); } else { - match args.next() { - None => {} - Some(_) => return Err(ShellError::string("Too many arguments")), + let rest: Vec = args.collect(); + + if rest.len() > 0 { + return Err(ShellError::string(&format!( + "Too many arguments, extras: {:?}", + rest + ))); } } - Ok(results) + Ok(Args { positional, named }) } #[allow(unused)] @@ -107,6 +164,49 @@ impl CommandConfig { } } +fn extract_named( + v: &mut Vec, + position: usize, + ty: &NamedValue, +) -> Result { + match ty { + NamedValue::Single => { + let expr = v.remove(position); + expect_simple_expr(expr) + } + + NamedValue::Tuple => { + let expr = v.remove(position); + let next = v.remove(position); + + let list = vec![expect_simple_expr(expr)?, expect_simple_expr(next)?]; + Ok(Value::List(list)) + } + + other => Err(ShellError::string(&format!( + "Unimplemented named argument {:?}", + other + ))), + } +} + +fn expect_simple_expr(expr: ast::Expression) -> Result { + match expr { + ast::Expression::Leaf(l) => Ok(match l { + ast::Leaf::Bare(s) => Value::string(s.to_string()), + ast::Leaf::String(s) => Value::string(s), + ast::Leaf::Boolean(b) => Value::boolean(b), + ast::Leaf::Int(i) => Value::int(i), + }), + + // TODO: Diagnostic + other => Err(ShellError::string(&format!( + "Expected a value, found {}", + other.print() + ))), + } +} + pub trait CommandRegistry { fn get(&self, name: &str) -> CommandConfig; }