From 94161fb07ad5cb8b91974abcd9f2e8db3f7aa40b Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy <mmstickman@gmail.com> Date: Sun, 18 Nov 2018 17:35:21 -0700 Subject: [PATCH] Add keys & maps methods for expanding maps --- examples/maps.out | 6 +- src/lib/lib.rs | 1 - src/lib/parser/mod.rs | 2 +- src/lib/parser/shell_expand/mod.rs | 17 +++-- .../shell_expand/words/methods/arrays.rs | 20 +++++- src/lib/shell/flow.rs | 2 +- src/lib/shell/mod.rs | 64 ++++++++++++++++++- src/lib/shell/variables/mod.rs | 24 ++++--- 8 files changed, 112 insertions(+), 24 deletions(-) diff --git a/examples/maps.out b/examples/maps.out index 42c1b13a..b6fd1d00 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 b37cf175..1d63a2ad 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 83ab4c53..12ccb77e 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 a12df51b..2104d20d 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 0e7269da..e2602879 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 53868800..8ffe9e37 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 622da121..326f8ce3 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 89b53f05..108a50b1 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 } -- GitLab