diff --git a/examples/maps.out b/examples/maps.out index 42c1b13adb78943c072c74fe300b82667c2315a9..b6fd1d002150debb221a9e3843566906726f9d48 100644 --- a/examples/maps.out +++ b/examples/maps.out @@ -6,9 +6,9 @@ three 2 1 2 3 4 5 6 7 8 -one three -one two three +key1 one key3 three +key1 one key2 two key3 three one two three -1.0 2.0 3.0 +ichi 1.0 ni 2.0 san 3.0 diff --git a/src/lib/lib.rs b/src/lib/lib.rs index b37cf175329bd190043cbaa395f78d25b7c1e086..1d63a2ade68852946b7199c606cbd915ccf5929d 100644 --- a/src/lib/lib.rs +++ b/src/lib/lib.rs @@ -1,5 +1,4 @@ #![allow(unknown_lints)] -#![allow(while_let_on_iterator)] #[macro_use] extern crate bitflags; diff --git a/src/lib/parser/mod.rs b/src/lib/parser/mod.rs index 83ab4c5358fd7ce5867ecfa448ab63290e33523a..12ccb77efb4ad04d6ae47820fcf4b759488476e7 100644 --- a/src/lib/parser/mod.rs +++ b/src/lib/parser/mod.rs @@ -8,7 +8,7 @@ mod statement; pub use self::quotes::Terminator; pub(crate) use self::{ loops::ForValueExpression, - shell_expand::{expand_string, Expander, Select}, + shell_expand::{expand_string, Expander, MapKeyIter, MapValueIter, Select}, statement::{parse_and_validate, StatementSplitter}, }; diff --git a/src/lib/parser/shell_expand/mod.rs b/src/lib/parser/shell_expand/mod.rs index a12df51b6cffc661a6bf9e62a9ef3476331917bd..2104d20de954b27eb7b28255ef6478aa5c3012b9 100644 --- a/src/lib/parser/shell_expand/mod.rs +++ b/src/lib/parser/shell_expand/mod.rs @@ -23,16 +23,25 @@ pub(crate) fn is_expression(s: &str) -> bool { || s.starts_with('\'') } +pub type MapKeyIter<'a> = Box<dyn Iterator<Item = &'a types::Str> + 'a>; +pub type MapValueIter<'a> = Box<dyn Iterator<Item = types::Str> + 'a>; + +// TODO: Make array expansions iterators instead of arrays. +// TODO: Use Cow<'a, types::Str> for hashmap values. /// Trait representing different elements of string expansion pub(crate) trait Expander { - /// Expand a tilde form to the correct directory + /// Expand a tilde form to the correct directory. fn tilde(&self, &str) -> Option<String> { None } - /// Expand an array variable with some selection + /// Expand an array variable with some selection. fn array(&self, &str, Select) -> Option<types::Array> { None } - /// Expand a string variable given if its quoted / unquoted + /// Expand a string variable given if it's quoted / unquoted fn string(&self, &str, bool) -> Option<types::Str> { None } - /// Expand a subshell expression + /// Expand a subshell expression. fn command(&self, &str) -> Option<types::Str> { None } + /// Iterating upon key-value maps. + fn map_keys<'a>(&'a self, &str, Select) -> Option<MapKeyIter> { None } + /// Iterating upon key-value maps. + fn map_values<'a>(&'a self, &str, Select) -> Option<MapValueIter> { None } } fn expand_process<E: Expander>( diff --git a/src/lib/parser/shell_expand/words/methods/arrays.rs b/src/lib/parser/shell_expand/words/methods/arrays.rs index 0e7269da930fbb6d0e4d13bdac26f2e41c666335..e2602879d5b096decf18741df21dc184c72df366 100644 --- a/src/lib/parser/shell_expand/words/methods/arrays.rs +++ b/src/lib/parser/shell_expand/words/methods/arrays.rs @@ -50,6 +50,18 @@ impl<'a> ArrayMethod<'a> { .select(self.selection.clone(), len)) } + fn map_keys<'b, E: Expander>(&self, expand_func: &'b E) -> Result<Array, &'static str> { + expand_func.map_keys(self.variable, self.selection.clone()) + .ok_or("no map found") + .map(|x| x.cloned().collect()) + } + + fn map_values<'b, E: Expander>(&self, expand_func: &'b E) -> Result<Array, &'static str> { + expand_func.map_values(self.variable, self.selection.clone()) + .ok_or("no map found") + .map(|x| x.collect()) + } + fn graphemes<E: Expander>(&self, expand_func: &E) -> Result<Array, &'static str> { let variable = self.resolve_var(expand_func); let graphemes: Vec<types::Str> = UnicodeSegmentation::graphemes(variable.as_str(), true) @@ -174,13 +186,15 @@ impl<'a> ArrayMethod<'a> { pub(crate) fn handle_as_array<E: Expander>(&self, expand_func: &E) -> Array { let res = match self.method { - "split" => self.split(expand_func), - "split_at" => self.split_at(expand_func), - "graphemes" => self.graphemes(expand_func), "bytes" => self.bytes(expand_func), "chars" => self.chars(expand_func), + "graphemes" => self.graphemes(expand_func), + "keys" => self.map_keys(expand_func), "lines" => self.lines(expand_func), "reverse" => self.reverse(expand_func), + "split_at" => self.split_at(expand_func), + "split" => self.split(expand_func), + "values" => self.map_values(expand_func), _ => Err("invalid array method"), }; diff --git a/src/lib/shell/flow.rs b/src/lib/shell/flow.rs index 53868800262e7f2a6374b62bc4c98f8b7957c9e0..8ffe9e37bf5954dd1193dc6bfe86da52e13e9e34 100644 --- a/src/lib/shell/flow.rs +++ b/src/lib/shell/flow.rs @@ -1,4 +1,4 @@ -use itertools::{Chunk, Itertools}; +use itertools::Itertools; use super::{ flags::*, flow_control::{insert_statement, Case, ElseIf, Function, Statement}, diff --git a/src/lib/shell/mod.rs b/src/lib/shell/mod.rs index 622da12154632f8b830f472090c37815e7226552..326f8ce33107271527da01cdeb7e2c0f930b57c9 100644 --- a/src/lib/shell/mod.rs +++ b/src/lib/shell/mod.rs @@ -44,7 +44,7 @@ use self::{ }; use builtins::{BuiltinMap, BUILTINS}; use liner::Context; -use parser::{pipelines::Pipeline, Expander, Select, Terminator}; +use parser::{pipelines::Pipeline, Expander, MapKeyIter, MapValueIter, Select, Terminator}; use std::{ fs::File, io::{self, Read, Write}, iter::FromIterator, ops::Deref, path::Path, process, sync::{atomic::Ordering, Arc, Mutex}, time::SystemTime, @@ -471,7 +471,8 @@ impl<'a> Expander for Shell { match selection { Select::All => { let mut array = types::Array::new(); - for (_, value) in hmap.iter() { + for (key, value) in hmap.iter() { + array.push(key.clone()); let f = format!("{}", value); match *value { VariableType::Str(_) => array.push(f.into()), @@ -499,7 +500,8 @@ impl<'a> Expander for Shell { match selection { Select::All => { let mut array = types::Array::new(); - for (_, value) in bmap.iter() { + for (key, value) in bmap.iter() { + array.push(key.clone()); let f = format!("{}", value); match *value { VariableType::Str(_) => array.push(f.into()), @@ -527,6 +529,62 @@ impl<'a> Expander for Shell { None } + fn map_keys<'b>(&'b self, name: &str, select: Select) -> Option<MapKeyIter<'b>> { + let nvalues; + let map: Box<dyn Iterator<Item = &'b types::Str>> = + match self.variables.get_ref(name) { + Some(&VariableType::HashMap(ref map)) => { + nvalues = map.len(); + Box::new(map.keys()) + } + Some(&VariableType::BTreeMap(ref map)) => { + nvalues = map.len(); + Box::new(map.keys()) + } + _ => return None + }; + + match select { + Select::All => return Some(map), + Select::Range(range) => if let Some((start, length)) = range.bounds(nvalues) { + if nvalues > start { + return Some(Box::new(map.skip(start).take(length))); + } + }, + _ => () + } + + None + } + + fn map_values<'b>(&'b self, name: &str, select: Select) -> Option<MapValueIter<'b>> { + let nvalues; + let map: Box<dyn Iterator<Item = types::Str>> = + match self.variables.get_ref(name) { + Some(&VariableType::HashMap(ref map)) => { + nvalues = map.len(); + Box::new(map.values().map(|x| format!("{}", x).into())) + } + Some(&VariableType::BTreeMap(ref map)) => { + nvalues = map.len(); + Box::new(map.values().map(|x| format!("{}", x).into())) + } + _ => return None + }; + + match select { + Select::All => return Some(map), + Select::Range(range) => if let Some((start, length)) = range.bounds(nvalues) { + if nvalues > start { + return Some(Box::new(map.skip(start).take(length))); + } + }, + _ => () + } + + None + } + fn tilde(&self, input: &str) -> Option<String> { self.variables.tilde_expansion(input, &self.directory_stack) } diff --git a/src/lib/shell/variables/mod.rs b/src/lib/shell/variables/mod.rs index 89b53f05b5d214f2f1fe3d4913c0327a58a992fa..108a50b1ed257e34797f20b893799afb8c3e54e9 100644 --- a/src/lib/shell/variables/mod.rs +++ b/src/lib/shell/variables/mod.rs @@ -315,27 +315,35 @@ impl Variables { } pub fn get_ref(&self, mut name: &str) -> Option<&VariableType> { - let mut up_namespace: isize = 0; - if name.starts_with("global::") { - name = &name["global::".len()..]; + const GLOBAL_NS: &str = "global::"; + const SUPER_NS: &str = "super::"; + + let mut up_namespace: isize = if name.starts_with(GLOBAL_NS) { + name = &name[GLOBAL_NS.len()..]; // Go up as many namespaces as possible - up_namespace = self.scopes().filter(|scope| scope.namespace).count() as isize; + self.scopes().filter(|scope| scope.namespace).count() as isize } else { - while name.starts_with("super::") { - name = &name["super::".len()..]; - up_namespace += 1; + let mut up = 0; + while name.starts_with(SUPER_NS) { + name = &name[SUPER_NS.len()..]; + up += 1; } - } + + up + }; + for scope in self.scopes() { match scope.get(name) { val @ Some(VariableType::Function(_)) => return val, val @ Some(_) if up_namespace == 0 => return val, _ => (), } + if scope.namespace { up_namespace -= 1; } } + None }