From 610a75e7da08c62f6ed7bd182265069795004aaa Mon Sep 17 00:00:00 2001
From: Michael Aaron Murphy <mmstickman@gmail.com>
Date: Fri, 7 Apr 2017 15:13:51 -0400
Subject: [PATCH] Split by Whitespaces in Array Split Method

Previously, when no pattern is supplied, a string will be split by spaces. This change
will extend that so that all whitespace characters will be considered in the pattern
for splitting.
---
 README.md                        |  33 +++++++++-
 src/parser/shell_expand/words.rs | 107 +++++++++++++++++++++++--------
 2 files changed, 112 insertions(+), 28 deletions(-)

diff --git a/README.md b/README.md
index 82f860fd..0bd4bad1 100644
--- a/README.md
+++ b/README.md
@@ -69,9 +69,9 @@ echo $A:$B
 echo ${A}s and ${B}s
 ```
 
-### Substrings
+### Substrings from Variables
 
-Ion natively supports splitting supplied strings by graphemes using the same slicing sytax for arrays:
+Ion natively supports splitting supplied strings by graphemes using the same slicing syntax for arrays:
 
 ```ion
 $ let string = "one two three"
@@ -260,6 +260,19 @@ let row = "one,two,three,four,five"
 echo @split(row, ',') # Splits by commas
 ```
 
+### Substring Slicing on String Methods
+
+```ion
+echo $join(array)[3..6]
+```
+
+### Array Slicing on Array Methods
+
+```ion
+let cpu_model = $(grep "model name" /proc/cpuinfo | head -1)
+echo @split(cpu_model)[3..5]
+```
+
 ### Commands
 
 Commands may be written line by line or altogether on the same line with semicolons separating them.
@@ -402,6 +415,22 @@ for i in @[echo 1 2 3]
 end
 ```
 
+### Slicing String-Based Command Substitutions
+
+You may slice the string returned to obtain its substring:
+
+```ion
+echo $(echo one two three)[..3]
+```
+
+### Slicing Array-Based Command Substitutions
+
+You may slice the array returned to obtained a specific set of elements:
+
+```ion
+echo @[grep "model name" /proc/cpuinfo | head -1][3..5]
+```
+
 ### Functions
 
 Functions in the Ion shell are defined with a name along with a set of variables. The function
diff --git a/src/parser/shell_expand/words.rs b/src/parser/shell_expand/words.rs
index 975ee59c..1d12ad20 100644
--- a/src/parser/shell_expand/words.rs
+++ b/src/parser/shell_expand/words.rs
@@ -1,4 +1,5 @@
 use std::io::{self, Write};
+use std::char;
 use std::str::FromStr;
 use super::{ExpanderFunctions, expand_string};
 use super::ranges::parse_index_range;
@@ -54,41 +55,73 @@ impl FromStr for Index {
     }
 }
 
+#[derive(Debug, PartialEq, Clone)]
+enum Pattern<'a> {
+    StringPattern(&'a str),
+    Whitespace,
+}
+
 #[derive(Debug, PartialEq, Clone)]
 pub struct ArrayMethod<'a> {
     method: &'a str,
     variable: &'a str,
-    pattern: &'a str,
+    pattern: Pattern<'a>,
     index: Index
 }
 
 impl<'a> ArrayMethod<'a> {
     pub fn handle(self, current: &mut String, expand_func: &ExpanderFunctions) {
-        let pattern = &expand_string(self.pattern, expand_func, false).join(" ");
         match self.method {
             "split" => if let Some(variable) = (expand_func.variable)(self.variable, false) {
-                match self.index {
-                    Index::All => current.push_str (
-                        &variable.split(pattern)
+                match (self.pattern, self.index) {
+                    (Pattern::StringPattern(pattern), Index::All) => current.push_str (
+                        &variable.split(&expand_string(pattern, expand_func, false).join(" "))
+                            .collect::<Vec<&str>>()
+                            .join(" ")
+                    ),
+                    (Pattern::Whitespace, Index::All) => current.push_str (
+                        &variable.split(char::is_whitespace)
                             .collect::<Vec<&str>>()
                             .join(" ")
                     ),
-                    Index::None => (),
-                    Index::ID(id) => current.push_str (
-                        variable.split(pattern)
+                    (_, Index::None) => (),
+                    (Pattern::StringPattern(pattern), Index::ID(id)) => current.push_str (
+                        variable.split(&expand_string(pattern, expand_func, false).join(" "))
                             .nth(id)
                             .unwrap_or_default()
                     ),
-                    Index::Range(start, IndexPosition::ID(end)) => {
-                        let range = variable.split(pattern).skip(start)
-                            .take(end-start)
+                    (Pattern::Whitespace, Index::ID(id)) => current.push_str (
+                        variable.split(char::is_whitespace)
+                            .nth(id)
+                            .unwrap_or_default()
+                    ),
+                    (Pattern::StringPattern(pattern), Index::Range(start, IndexPosition::ID(end))) => {
+                        let range = variable.split(&expand_string(pattern, expand_func, false).join(" "))
+                            .skip(start).take(end-start)
+                            .collect::<Vec<&str>>()
+                            .join(" ");
+
+                        current.push_str(&range);
+                    },
+                    (Pattern::Whitespace, Index::Range(start, IndexPosition::ID(end))) => {
+                        let range = variable.split(char::is_whitespace)
+                            .skip(start).take(end-start)
                             .collect::<Vec<&str>>()
                             .join(" ");
 
                         current.push_str(&range);
                     },
-                    Index::Range(start, IndexPosition::CatchAll) => {
-                        let range = variable.split(pattern).skip(start)
+                    (Pattern::StringPattern(pattern), Index::Range(start, IndexPosition::CatchAll)) => {
+                        let range = variable.split(&expand_string(pattern, expand_func, false).join(" "))
+                            .skip(start)
+                            .collect::<Vec<&str>>()
+                            .join(" ");
+
+                        current.push_str(&range);
+                    }
+                    (Pattern::Whitespace, Index::Range(start, IndexPosition::CatchAll)) => {
+                        let range = variable.split(char::is_whitespace)
+                            .skip(start)
                             .collect::<Vec<&str>>()
                             .join(" ");
 
@@ -105,24 +138,46 @@ impl<'a> ArrayMethod<'a> {
     }
 
     pub fn handle_as_array(self, expand_func: &ExpanderFunctions) -> Vec<String> {
-        let pattern = &expand_string(self.pattern, expand_func, false).join(" ");
         match self.method {
             "split" => if let Some(variable) = (expand_func.variable)(self.variable, false) {
-                return match self.index {
-                    Index::All => variable.split(pattern)
+                return match (self.pattern, self.index) {
+                    (_, Index::None) => vec!["".to_owned()],
+                    (Pattern::StringPattern(pattern), Index::All) => variable
+                        .split(&expand_string(pattern, expand_func, false).join(" "))
                         .map(String::from)
                         .collect::<Vec<String>>(),
-                    Index::None => vec!["".to_owned()],
-                    Index::ID(id) => vec![variable.split(pattern)
+                    (Pattern::Whitespace, Index::All) => variable
+                        .split(char::is_whitespace)
+                        .map(String::from)
+                        .collect::<Vec<String>>(),
+                    (Pattern::StringPattern(pattern), Index::ID(id)) => vec![variable
+                        .split(&expand_string(pattern, expand_func, false).join(" "))
+                        .nth(id).map(String::from)
+                        .unwrap_or_default()],
+                    (Pattern::Whitespace, Index::ID(id)) => vec![variable
+                        .split(char::is_whitespace)
                         .nth(id).map(String::from)
                         .unwrap_or_default()],
-                    Index::Range(start, IndexPosition::CatchAll) => {
-                        variable.split(pattern).skip(start)
+                    (Pattern::StringPattern(pattern), Index::Range(start, IndexPosition::CatchAll)) => {
+                        variable.split(&expand_string(pattern, expand_func, false).join(" "))
+                            .skip(start)
+                            .map(String::from)
+                            .collect::<Vec<String>>()
+                    },
+                    (Pattern::Whitespace, Index::Range(start, IndexPosition::CatchAll)) => {
+                        variable.split(char::is_whitespace)
+                            .skip(start)
+                            .map(String::from)
+                            .collect::<Vec<String>>()
+                    },
+                    (Pattern::StringPattern(pattern), Index::Range(start, IndexPosition::ID(end))) => {
+                        variable.split(&expand_string(pattern, expand_func, false).join(" ")).skip(start)
+                            .take(end-start)
                             .map(String::from)
                             .collect::<Vec<String>>()
                     },
-                    Index::Range(start, IndexPosition::ID(end)) => {
-                        variable.split(pattern).skip(start)
+                    (Pattern::Whitespace, Index::Range(start, IndexPosition::ID(end))) => {
+                        variable.split(char::is_whitespace).skip(start)
                             .take(end-start)
                             .map(String::from)
                             .collect::<Vec<String>>()
@@ -331,14 +386,14 @@ impl<'a> WordIterator<'a> {
                                         WordToken::ArrayMethod(ArrayMethod {
                                             method: method,
                                             variable: variable,
-                                            pattern: pattern,
+                                            pattern: Pattern::StringPattern(pattern),
                                             index: self.read_index(iterator)
                                         })
                                     } else {
                                         WordToken::ArrayMethod(ArrayMethod {
                                             method: method,
                                             variable: variable,
-                                            pattern: pattern,
+                                            pattern: Pattern::StringPattern(pattern),
                                             index: Index::All
                                         })
                                     }
@@ -355,14 +410,14 @@ impl<'a> WordIterator<'a> {
                                 WordToken::ArrayMethod(ArrayMethod {
                                     method: method,
                                     variable: variable,
-                                    pattern: " ",
+                                    pattern: Pattern::Whitespace,
                                     index: self.read_index(iterator)
                                 })
                             } else {
                                 WordToken::ArrayMethod(ArrayMethod {
                                     method: method,
                                     variable: variable,
-                                    pattern: " ",
+                                    pattern: Pattern::Whitespace,
                                     index: Index::All
                                 })
                             }
-- 
GitLab