From dc24e41a600ccb7090392005924639c37bffdd31 Mon Sep 17 00:00:00 2001
From: Michael Murphy <mmstickman@gmail.com>
Date: Sat, 23 Jun 2018 11:56:31 -0600
Subject: [PATCH] Create ion_{braces,ranges} crates

---
 .gitlab-ci.yml                                |   3 +
 Cargo.lock                                    |  18 +-
 Cargo.toml                                    |   5 +-
 ion_braces/Cargo.toml                         |   8 +
 ion_braces/src/lib.rs                         | 200 ++++++++++++++++
 ion_builtins/src/test.rs                      |  26 +--
 ion_ranges/Cargo.toml                         |   7 +
 .../words => ion_ranges/src}/index.rs         |   6 +-
 ion_ranges/src/lib.rs                         | 217 ++++++++++++++++++
 .../ranges.rs => ion_ranges/src/parse.rs      | 197 +---------------
 .../words => ion_ranges/src}/range.rs         |  12 +-
 .../words => ion_ranges/src}/select.rs        |  11 +-
 ion_sys/src/sys/unix/mod.rs                   |   4 +-
 src/lib/lib.rs                                |   2 +
 src/lib/parser/shell_expand/mod.rs            |  16 +-
 .../shell_expand/words/methods/arrays.rs      |  10 +-
 .../parser/shell_expand/words/methods/mod.rs  |  12 -
 src/lib/parser/shell_expand/words/mod.rs      |  11 +-
 src/lib/parser/shell_expand/words/tests.rs    |  19 +-
 src/lib/shell/mod.rs                          |   2 +-
 20 files changed, 510 insertions(+), 276 deletions(-)
 create mode 100644 ion_braces/Cargo.toml
 create mode 100644 ion_braces/src/lib.rs
 create mode 100644 ion_ranges/Cargo.toml
 rename {src/lib/parser/shell_expand/words => ion_ranges/src}/index.rs (87%)
 create mode 100644 ion_ranges/src/lib.rs
 rename src/lib/parser/shell_expand/ranges.rs => ion_ranges/src/parse.rs (67%)
 rename {src/lib/parser/shell_expand/words => ion_ranges/src}/range.rs (84%)
 rename {src/lib/parser/shell_expand/words => ion_ranges/src}/select.rs (89%)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index eddb2088..88107b83 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -8,4 +8,7 @@ nightly:
   script:
     - cargo build
     - cargo test --lib
+    - cargo test --manifest-path ion_braces/Cargo.toml
+    - cargo test --manifest-path ion_builtins/Cargo.toml
+    - cargo test --manifest-path ion_ranges/Cargo.toml
     - bash examples/run_examples.sh
diff --git a/Cargo.lock b/Cargo.lock
index 68fe9b62..9ac140e8 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -165,13 +165,14 @@ dependencies = [
  "failure_derive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
+ "ion_braces 0.1.0",
  "ion_builtins 0.1.0",
+ "ion_ranges 0.1.0",
  "ion_sys 0.1.0",
  "itoa 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "libloading 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "liner 0.4.5 (git+https://gitlab.redox-os.org/redox-os/liner)",
- "permutate 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "rand 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "regex 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "smallstring 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -181,6 +182,14 @@ dependencies = [
  "xdg 2.1.0 (git+https://github.com/whitequark/rust-xdg)",
 ]
 
+[[package]]
+name = "ion_braces"
+version = "0.1.0"
+dependencies = [
+ "permutate 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "smallvec 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "ion_builtins"
 version = "0.1.0"
@@ -192,6 +201,13 @@ dependencies = [
  "smallvec 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "ion_ranges"
+version = "0.1.0"
+dependencies = [
+ "smallstring 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "ion_sys"
 version = "0.1.0"
diff --git a/Cargo.toml b/Cargo.toml
index ee2c9710..a19ca3b5 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -17,7 +17,7 @@ repository = "https://gitlab.redox-os.org/redox-os/ion"
 version = "1.0.0-alpha"
 
 [workspace]
-members = [ "ion_builtins", "ion_sys" ]
+members = [ "ion_braces", "ion_builtins", "ion_sys", "ion_ranges" ]
 
 [[bin]]
 name = "ion"
@@ -37,15 +37,16 @@ glob = "0.2"
 itoa = "0.4"
 lazy_static = "1.0"
 liner = { git = "https://gitlab.redox-os.org/redox-os/liner" }
-permutate = "0.3"
 rand = "0.5"
 regex = "1.0"
 smallstring = "0.1"
 smallvec = "0.6"
 unicode-segmentation = "1.2"
 xdg = { git = "https://github.com/whitequark/rust-xdg" }
+ion_braces = { path = "ion_braces" }
 ion_builtins = { path = "ion_builtins" }
 ion_sys = { path = "ion_sys" }
+ion_ranges = { path = "ion_ranges" }
 
 [lib]
 path = "src/lib/lib.rs"
diff --git a/ion_braces/Cargo.toml b/ion_braces/Cargo.toml
new file mode 100644
index 00000000..97800764
--- /dev/null
+++ b/ion_braces/Cargo.toml
@@ -0,0 +1,8 @@
+[package]
+name = "ion_braces"
+version = "0.1.0"
+authors = ["Michael Murphy <mmstickman@gmail.com>"]
+
+[dependencies]
+permutate = "0.3"
+smallvec = "0.6"
diff --git a/ion_braces/src/lib.rs b/ion_braces/src/lib.rs
new file mode 100644
index 00000000..16b48149
--- /dev/null
+++ b/ion_braces/src/lib.rs
@@ -0,0 +1,200 @@
+extern crate permutate;
+extern crate smallvec;
+
+use permutate::Permutator;
+use smallvec::SmallVec;
+
+#[derive(Debug)]
+/// A token primitive for the `expand_braces` function.
+pub enum BraceToken {
+    Normal(String),
+    Expander,
+}
+
+pub fn expand<'a>(
+    tokens: &'a [BraceToken],
+    expanders: &'a [&'a [&'a str]],
+) -> Box<Iterator<Item = String> + 'a> {
+    if expanders.len() > 1 {
+        let multiple_brace_expand = MultipleBraceExpand::new(tokens, expanders);
+        Box::new(multiple_brace_expand)
+    } else if expanders.len() == 1 {
+        let single_brace_expand = SingleBraceExpand {
+            elements:   expanders[0].iter().map(|element| *element),
+            tokens,
+            loop_count: 0,
+        };
+        Box::new(single_brace_expand)
+    } else {
+        Box::new(::std::iter::empty())
+    }
+}
+
+fn escape_string(output: &mut SmallVec<[u8; 64]>, input: &str) {
+    let mut backslash = false;
+    for character in input.bytes() {
+        if backslash {
+            match character {
+                b'{' | b'}' | b',' => output.push(character),
+                _ => {
+                    output.push(b'\\');
+                    output.push(character);
+                }
+            }
+            backslash = false;
+        } else if character == b'\\' {
+            backslash = true;
+        } else {
+            output.push(character);
+        }
+    }
+}
+
+pub struct MultipleBraceExpand<'a> {
+    permutator: Permutator<'a, str>,
+    tokens:     &'a [BraceToken],
+    buffer:     Vec<&'a str>
+}
+
+impl<'a> MultipleBraceExpand<'a> {
+    pub fn new(
+        tokens: &'a [BraceToken],
+        expanders: &'a [&'a [&'a str]]
+    ) -> MultipleBraceExpand<'a> {
+        MultipleBraceExpand {
+            permutator: Permutator::new(expanders),
+            tokens,
+            buffer: vec![""; expanders.len()]
+        }
+    }
+}
+
+impl<'a> Iterator for MultipleBraceExpand<'a> {
+    type Item = String;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        if self.permutator.next_with_buffer(&mut self.buffer) {
+            let mut strings = self.buffer.iter();
+            let small_vec: SmallVec<[u8; 64]> = self.tokens.iter().fold(
+                SmallVec::with_capacity(64),
+                |mut small_vec, token| match *token {
+                    BraceToken::Normal(ref text) => {
+                        escape_string(&mut small_vec, text);
+                        small_vec
+                    }
+                    BraceToken::Expander => {
+                        escape_string(&mut small_vec, strings.next().unwrap());
+                        small_vec
+                    }
+                },
+            );
+            Some(unsafe { String::from_utf8_unchecked(small_vec.to_vec()) })
+        } else {
+            None
+        }
+    }
+}
+
+pub struct SingleBraceExpand<'a, 'b, I>
+where
+    I: Iterator<Item = &'a str>,
+{
+    elements:   I,
+    tokens:     &'b [BraceToken],
+    loop_count: usize,
+}
+
+impl<'a, 'b, I> Iterator for SingleBraceExpand<'a, 'b, I>
+where
+    I: Iterator<Item = &'a str>,
+{
+    type Item = String;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        match self.loop_count {
+            0 => {
+                let small_vec: SmallVec<[u8; 64]> = self.tokens.iter().fold(
+                    SmallVec::with_capacity(64),
+                    |mut small_vec, token| match *token {
+                        BraceToken::Normal(ref text) => {
+                            escape_string(&mut small_vec, text);
+                            small_vec
+                        }
+                        BraceToken::Expander => {
+                            escape_string(&mut small_vec, self.elements.next().unwrap());
+                            small_vec
+                        }
+                    },
+                );
+                self.loop_count = 1;
+                Some(unsafe { String::from_utf8_unchecked(small_vec.to_vec()) })
+            }
+            _ => {
+                if let Some(element) = self.elements.next() {
+                    let small_vec: SmallVec<[u8; 64]> = self.tokens.iter().fold(
+                        SmallVec::with_capacity(64),
+                        |mut small_vec, token| match *token {
+                            BraceToken::Normal(ref text) => {
+                                escape_string(&mut small_vec, text);
+                                small_vec
+                            }
+                            BraceToken::Expander => {
+                                escape_string(&mut small_vec, element);
+                                small_vec
+                            }
+                        },
+                    );
+                    Some(unsafe { String::from_utf8_unchecked(small_vec.to_vec()) })
+                } else {
+                    None
+                }
+            }
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_multiple_brace_expand() {
+        let expanders: &[&[&str]] = &[&["1", "2"][..], &["3", "4"][..], &["5", "6"][..]];
+        let tokens: &[BraceToken] = &[
+            BraceToken::Normal("AB".to_owned()),
+            BraceToken::Expander,
+            BraceToken::Normal("CD".to_owned()),
+            BraceToken::Expander,
+            BraceToken::Normal("EF".to_owned()),
+            BraceToken::Expander,
+            BraceToken::Normal("GH".to_owned()),
+        ];
+        assert_eq!(
+            MultipleBraceExpand ::new(tokens, expanders).collect::<Vec<String>>(),
+            vec![
+                "AB1CD3EF5GH".to_owned(),
+                "AB1CD3EF6GH".to_owned(),
+                "AB1CD4EF5GH".to_owned(),
+                "AB1CD4EF6GH".to_owned(),
+                "AB2CD3EF5GH".to_owned(),
+                "AB2CD3EF6GH".to_owned(),
+                "AB2CD4EF5GH".to_owned(),
+                "AB2CD4EF6GH".to_owned(),
+            ]
+        );
+    }
+
+    #[test]
+    fn test_single_brace_expand() {
+        let elements = &["one", "two", "three"];
+        let tokens: &[BraceToken] = &[BraceToken::Normal("A=".to_owned()), BraceToken::Expander];
+        assert_eq!(
+            SingleBraceExpand {
+                elements:   elements.iter().map(|element| *element),
+                tokens,
+                loop_count: 0,
+            }.collect::<Vec<String>>(),
+            vec!["A=one".to_owned(), "A=two".to_owned(), "A=three".to_owned()]
+        );
+    }
+}
diff --git a/ion_builtins/src/test.rs b/ion_builtins/src/test.rs
index df246358..1b58a5f3 100644
--- a/ion_builtins/src/test.rs
+++ b/ion_builtins/src/test.rs
@@ -282,7 +282,7 @@ fn file_has_read_permission(filepath: &str) -> bool {
 /// To extract the permissions from the mode, the bitwise AND operator will be used and compared
 /// with the respective write bits.
 fn file_has_write_permission(filepath: &str) -> bool {
-    const USER: u32 = 0b10000000;
+    const USER: u32 = 0b1000_0000;
     const GROUP: u32 = 0b10000;
     const GUEST: u32 = 0b10;
 
@@ -299,7 +299,7 @@ fn file_has_write_permission(filepath: &str) -> bool {
 /// Note: This function is 1:1 the same as src/builtins/exists.rs:file_has_execute_permission
 /// If you change the following function, please also update the one in src/builtins/exists.rs
 fn file_has_execute_permission(filepath: &str) -> bool {
-    const USER: u32 = 0b1000000;
+    const USER: u32 = 0b100_0000;
     const GROUP: u32 = 0b1000;
     const GUEST: u32 = 0b1;
 
@@ -423,39 +423,39 @@ fn test_integers_arguments() {
 
 #[test]
 fn test_file_exists() {
-    assert_eq!(file_exists("testing/empty_file"), true);
+    assert_eq!(file_exists("../testing/empty_file"), true);
     assert_eq!(file_exists("this-does-not-exist"), false);
 }
 
 #[test]
 fn test_file_is_regular() {
-    assert_eq!(file_is_regular("testing/empty_file"), true);
-    assert_eq!(file_is_regular("testing"), false);
+    assert_eq!(file_is_regular("../testing/empty_file"), true);
+    assert_eq!(file_is_regular("../testing"), false);
 }
 
 #[test]
 fn test_file_is_directory() {
-    assert_eq!(file_is_directory("testing"), true);
-    assert_eq!(file_is_directory("testing/empty_file"), false);
+    assert_eq!(file_is_directory("../testing"), true);
+    assert_eq!(file_is_directory("../testing/empty_file"), false);
 }
 
 #[test]
 fn test_file_is_symlink() {
-    assert_eq!(file_is_symlink("testing/symlink"), true);
-    assert_eq!(file_is_symlink("testing/empty_file"), false);
+    assert_eq!(file_is_symlink("../testing/symlink"), true);
+    assert_eq!(file_is_symlink("../testing/empty_file"), false);
 }
 
 #[test]
 fn test_file_has_execute_permission() {
-    assert_eq!(file_has_execute_permission("testing/executable_file"), true);
-    assert_eq!(file_has_execute_permission("testing/empty_file"), false);
+    assert_eq!(file_has_execute_permission("../testing/executable_file"), true);
+    assert_eq!(file_has_execute_permission("../testing/empty_file"), false);
 }
 
 #[test]
 fn test_file_size_is_greater_than_zero() {
     assert_eq!(
-        file_size_is_greater_than_zero("testing/file_with_text"),
+        file_size_is_greater_than_zero("../testing/file_with_text"),
         true
     );
-    assert_eq!(file_size_is_greater_than_zero("testing/empty_file"), false);
+    assert_eq!(file_size_is_greater_than_zero("../testing/empty_file"), false);
 }
diff --git a/ion_ranges/Cargo.toml b/ion_ranges/Cargo.toml
new file mode 100644
index 00000000..bdedec44
--- /dev/null
+++ b/ion_ranges/Cargo.toml
@@ -0,0 +1,7 @@
+[package]
+name = "ion_ranges"
+version = "0.1.0"
+authors = ["Michael Murphy <mmstickman@gmail.com>"]
+
+[dependencies]
+smallstring = "0.1"
diff --git a/src/lib/parser/shell_expand/words/index.rs b/ion_ranges/src/index.rs
similarity index 87%
rename from src/lib/parser/shell_expand/words/index.rs
rename to ion_ranges/src/index.rs
index dcd9a7b6..5dc172fe 100644
--- a/src/lib/parser/shell_expand/words/index.rs
+++ b/ion_ranges/src/index.rs
@@ -1,6 +1,6 @@
 /// Index into a vector-like object
 #[derive(Debug, PartialEq, Copy, Clone)]
-pub(crate) enum Index {
+pub enum Index {
     /// Index starting from the beginning of the vector, where `Forward(0)`
     /// is the first element
     Forward(usize),
@@ -10,7 +10,7 @@ pub(crate) enum Index {
 }
 
 impl Index {
-    pub(crate) fn resolve(&self, vector_length: usize) -> Option<usize> {
+    pub fn resolve(&self, vector_length: usize) -> Option<usize> {
         match *self {
             Index::Forward(n) => Some(n),
             Index::Backward(n) => if n >= vector_length {
@@ -27,7 +27,7 @@ impl Index {
     /// ```ignore,rust
     /// assert_eq!(Index::new(-1), Index::Backward(0))
     /// ```
-    pub(crate) fn new(input: isize) -> Index {
+    pub fn new(input: isize) -> Index {
         if input < 0 {
             Index::Backward((input.abs() as usize) - 1)
         } else {
diff --git a/ion_ranges/src/lib.rs b/ion_ranges/src/lib.rs
new file mode 100644
index 00000000..86d06ae7
--- /dev/null
+++ b/ion_ranges/src/lib.rs
@@ -0,0 +1,217 @@
+extern crate smallstring;
+
+mod index;
+mod parse;
+mod range;
+mod select;
+
+pub use self::index::*;
+pub use self::parse::*;
+pub use self::range::*;
+pub use self::select::*;
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn ranges() {
+        let range1 = Range::exclusive(Index::new(1), Index::new(5));
+        assert_eq!(Some((1, 4)), range1.bounds(42));
+        assert_eq!(Some((1, 4)), range1.bounds(7));
+        let range2 = Range::inclusive(Index::new(2), Index::new(-4));
+        assert_eq!(Some((2, 5)), range2.bounds(10));
+        assert_eq!(None, range2.bounds(3));
+    }
+
+    #[test]
+    fn index_ranges() {
+        let valid_cases = vec![
+            (
+                Range::exclusive(Index::Forward(0), Index::Forward(3)),
+                "0..3",
+            ),
+            (
+                Range::inclusive(Index::Forward(0), Index::Forward(2)),
+                "0...2",
+            ),
+            (
+                Range::inclusive(Index::Forward(2), Index::Backward(1)),
+                "2...-2",
+            ),
+            (
+                Range::inclusive(Index::Forward(0), Index::Backward(0)),
+                "0...-1",
+            ),
+            (
+                Range::exclusive(Index::Backward(2), Index::Backward(0)),
+                "-3..-1",
+            ),
+            (Range::from(Index::Backward(2)), "-3.."),
+            (Range::to(Index::Forward(5)), "..5"),
+        ];
+
+        for (range, string) in valid_cases {
+            assert_eq!(Some(range), parse_index_range(string));
+        }
+
+        let invalid_cases = vec!["0..A", "3-3..42"];
+
+        for range in invalid_cases {
+            assert_eq!(None, parse_index_range(range))
+        }
+    }
+
+    #[test]
+    fn range_expand() {
+        if let Some(_) = parse_range("abc") {
+            panic!("parse_range() failed");
+        }
+
+        let actual: Vec<String> = parse_range("-3...3").unwrap().collect();
+        let expected: Vec<String> = vec![
+            "-3".to_owned(),
+            "-2".to_owned(),
+            "-1".to_owned(),
+            "0".to_owned(),
+            "1".to_owned(),
+            "2".to_owned(),
+            "3".to_owned(),
+        ];
+
+        assert_eq!(actual, expected);
+
+        let actual: Vec<String> = parse_range("07...12").unwrap().collect();
+        let expected: Vec<String> = vec![
+            "07".to_owned(),
+            "08".to_owned(),
+            "09".to_owned(),
+            "10".to_owned(),
+            "11".to_owned(),
+            "12".to_owned(),
+        ];
+
+        assert_eq!(actual, expected);
+
+        let actual: Vec<String> = parse_range("-3...10").unwrap().collect();
+        let expected: Vec<String> = vec![
+            "-3".to_owned(),
+            "-2".to_owned(),
+            "-1".to_owned(),
+            "0".to_owned(),
+            "1".to_owned(),
+            "2".to_owned(),
+            "3".to_owned(),
+            "4".to_owned(),
+            "5".to_owned(),
+            "6".to_owned(),
+            "7".to_owned(),
+            "8".to_owned(),
+            "9".to_owned(),
+            "10".to_owned(),
+        ];
+
+        assert_eq!(actual, expected);
+
+        let actual: Vec<String> = parse_range("3...-3").unwrap().collect();
+        let expected: Vec<String> = vec![
+            "3".to_owned(),
+            "2".to_owned(),
+            "1".to_owned(),
+            "0".to_owned(),
+            "-1".to_owned(),
+            "-2".to_owned(),
+            "-3".to_owned(),
+        ];
+
+        assert_eq!(actual, expected);
+
+        let actual: Vec<String> = parse_range("03...-3").unwrap().collect();
+        let expected: Vec<String> = vec![
+            "03".to_owned(),
+            "02".to_owned(),
+            "01".to_owned(),
+            "00".to_owned(),
+            "-1".to_owned(),
+            "-2".to_owned(),
+            "-3".to_owned(),
+        ];
+
+        assert_eq!(actual, expected);
+
+        let actual: Vec<String> = parse_range("3...-03").unwrap().collect();
+        let expected: Vec<String> = vec![
+            "003".to_owned(),
+            "002".to_owned(),
+            "001".to_owned(),
+            "000".to_owned(),
+            "-01".to_owned(),
+            "-02".to_owned(),
+            "-03".to_owned(),
+        ];
+
+        assert_eq!(actual, expected);
+
+        let actual: Vec<String> = parse_range("a...c").unwrap().collect();
+        let expected: Vec<String> = vec!["a".to_owned(), "b".to_owned(), "c".to_owned()];
+
+        assert_eq!(actual, expected);
+
+        let actual: Vec<String> = parse_range("c...a").unwrap().collect();
+        let expected: Vec<String> = vec!["c".to_owned(), "b".to_owned(), "a".to_owned()];
+
+        assert_eq!(actual, expected);
+
+        let actual: Vec<String> = parse_range("A...C").unwrap().collect();
+        let expected: Vec<String> = vec!["A".to_owned(), "B".to_owned(), "C".to_owned()];
+
+        assert_eq!(actual, expected);
+
+        let actual: Vec<String> = parse_range("C...A").unwrap().collect();
+        let expected: Vec<String> = vec!["C".to_owned(), "B".to_owned(), "A".to_owned()];
+
+        assert_eq!(actual, expected);
+
+        let actual: Vec<String> = parse_range("C..A").unwrap().collect();
+        let expected: Vec<String> = vec!["C".to_owned(), "B".to_owned()];
+        assert_eq!(actual, expected);
+
+        let actual: Vec<String> = parse_range("c..a").unwrap().collect();
+        let expected: Vec<String> = vec!["c".to_owned(), "b".to_owned()];
+        assert_eq!(actual, expected);
+
+        let actual: Vec<String> = parse_range("-3..4").unwrap().collect();
+        let expected: Vec<String> = vec![
+            "-3".to_owned(),
+            "-2".to_owned(),
+            "-1".to_owned(),
+            "0".to_owned(),
+            "1".to_owned(),
+            "2".to_owned(),
+            "3".to_owned(),
+        ];
+
+        assert_eq!(actual, expected);
+
+        let actual: Vec<String> = parse_range("3..-4").unwrap().collect();
+        let expected: Vec<String> = vec![
+            "3".to_owned(),
+            "2".to_owned(),
+            "1".to_owned(),
+            "0".to_owned(),
+            "-1".to_owned(),
+            "-2".to_owned(),
+            "-3".to_owned(),
+        ];
+
+        assert_eq!(actual, expected);
+
+        let actual: Vec<String> = parse_range("-3...0").unwrap().collect();
+        let expected: Vec<String> = vec!["-3".into(), "-2".into(), "-1".into(), "0".into()];
+        assert_eq!(actual, expected);
+
+        let actual: Vec<String> = parse_range("-3..0").unwrap().collect();
+        let expected: Vec<String> = vec!["-3".into(), "-2".into(), "-1".into()];
+        assert_eq!(actual, expected);
+    }
+}
diff --git a/src/lib/parser/shell_expand/ranges.rs b/ion_ranges/src/parse.rs
similarity index 67%
rename from src/lib/parser/shell_expand/ranges.rs
rename to ion_ranges/src/parse.rs
index a01d2b48..de7312a2 100644
--- a/src/lib/parser/shell_expand/ranges.rs
+++ b/ion_ranges/src/parse.rs
@@ -1,4 +1,4 @@
-use super::words::{Index, Range};
+use super::{Index, Range};
 use std::cmp::Ordering;
 
 fn stepped_range_numeric<'a>(
@@ -163,7 +163,7 @@ fn strings_to_isizes(a: &str, b: &str) -> Option<(isize, isize, usize)> {
 //      Inclusive nonstepped: {start...end}
 //      Exclusive stepped: {start..step..end}
 //      Inclusive stepped: {start..step...end}
-pub(crate) fn parse_range<'a>(input: &str) -> Option<Box<Iterator<Item = String> + 'a>> {
+pub fn parse_range<'a>(input: &str) -> Option<Box<Iterator<Item = String> + 'a>> {
     let mut read = 0;
     let mut bytes_iterator = input.bytes();
     while let Some(byte) = bytes_iterator.next() {
@@ -277,7 +277,7 @@ pub(crate) fn parse_range<'a>(input: &str) -> Option<Box<Iterator<Item = String>
     None
 }
 
-pub(crate) fn parse_index_range(input: &str) -> Option<Range> {
+pub fn parse_index_range(input: &str) -> Option<Range> {
     let mut bytes_iterator = input.bytes().enumerate();
     while let Some((id, byte)) = bytes_iterator.next() {
         match byte {
@@ -336,194 +336,3 @@ pub(crate) fn parse_index_range(input: &str) -> Option<Range> {
 
     None
 }
-
-#[test]
-fn index_ranges() {
-    let valid_cases = vec![
-        (
-            Range::exclusive(Index::Forward(0), Index::Forward(3)),
-            "0..3",
-        ),
-        (
-            Range::inclusive(Index::Forward(0), Index::Forward(2)),
-            "0...2",
-        ),
-        (
-            Range::inclusive(Index::Forward(2), Index::Backward(1)),
-            "2...-2",
-        ),
-        (
-            Range::inclusive(Index::Forward(0), Index::Backward(0)),
-            "0...-1",
-        ),
-        (
-            Range::exclusive(Index::Backward(2), Index::Backward(0)),
-            "-3..-1",
-        ),
-        (Range::from(Index::Backward(2)), "-3.."),
-        (Range::to(Index::Forward(5)), "..5"),
-    ];
-
-    for (range, string) in valid_cases {
-        assert_eq!(Some(range), parse_index_range(string));
-    }
-
-    let invalid_cases = vec!["0..A", "3-3..42"];
-
-    for range in invalid_cases {
-        assert_eq!(None, parse_index_range(range))
-    }
-}
-
-#[test]
-fn range_expand() {
-    if let Some(_) = parse_range("abc") {
-        panic!("parse_range() failed");
-    }
-
-    let actual: Vec<String> = parse_range("-3...3").unwrap().collect();
-    let expected: Vec<String> = vec![
-        "-3".to_owned(),
-        "-2".to_owned(),
-        "-1".to_owned(),
-        "0".to_owned(),
-        "1".to_owned(),
-        "2".to_owned(),
-        "3".to_owned(),
-    ];
-
-    assert_eq!(actual, expected);
-
-    let actual: Vec<String> = parse_range("07...12").unwrap().collect();
-    let expected: Vec<String> = vec![
-        "07".to_owned(),
-        "08".to_owned(),
-        "09".to_owned(),
-        "10".to_owned(),
-        "11".to_owned(),
-        "12".to_owned(),
-    ];
-
-    assert_eq!(actual, expected);
-
-    let actual: Vec<String> = parse_range("-3...10").unwrap().collect();
-    let expected: Vec<String> = vec![
-        "-3".to_owned(),
-        "-2".to_owned(),
-        "-1".to_owned(),
-        "0".to_owned(),
-        "1".to_owned(),
-        "2".to_owned(),
-        "3".to_owned(),
-        "4".to_owned(),
-        "5".to_owned(),
-        "6".to_owned(),
-        "7".to_owned(),
-        "8".to_owned(),
-        "9".to_owned(),
-        "10".to_owned(),
-    ];
-
-    assert_eq!(actual, expected);
-
-    let actual: Vec<String> = parse_range("3...-3").unwrap().collect();
-    let expected: Vec<String> = vec![
-        "3".to_owned(),
-        "2".to_owned(),
-        "1".to_owned(),
-        "0".to_owned(),
-        "-1".to_owned(),
-        "-2".to_owned(),
-        "-3".to_owned(),
-    ];
-
-    assert_eq!(actual, expected);
-
-    let actual: Vec<String> = parse_range("03...-3").unwrap().collect();
-    let expected: Vec<String> = vec![
-        "03".to_owned(),
-        "02".to_owned(),
-        "01".to_owned(),
-        "00".to_owned(),
-        "-1".to_owned(),
-        "-2".to_owned(),
-        "-3".to_owned(),
-    ];
-
-    assert_eq!(actual, expected);
-
-    let actual: Vec<String> = parse_range("3...-03").unwrap().collect();
-    let expected: Vec<String> = vec![
-        "003".to_owned(),
-        "002".to_owned(),
-        "001".to_owned(),
-        "000".to_owned(),
-        "-01".to_owned(),
-        "-02".to_owned(),
-        "-03".to_owned(),
-    ];
-
-    assert_eq!(actual, expected);
-
-    let actual: Vec<String> = parse_range("a...c").unwrap().collect();
-    let expected: Vec<String> = vec!["a".to_owned(), "b".to_owned(), "c".to_owned()];
-
-    assert_eq!(actual, expected);
-
-    let actual: Vec<String> = parse_range("c...a").unwrap().collect();
-    let expected: Vec<String> = vec!["c".to_owned(), "b".to_owned(), "a".to_owned()];
-
-    assert_eq!(actual, expected);
-
-    let actual: Vec<String> = parse_range("A...C").unwrap().collect();
-    let expected: Vec<String> = vec!["A".to_owned(), "B".to_owned(), "C".to_owned()];
-
-    assert_eq!(actual, expected);
-
-    let actual: Vec<String> = parse_range("C...A").unwrap().collect();
-    let expected: Vec<String> = vec!["C".to_owned(), "B".to_owned(), "A".to_owned()];
-
-    assert_eq!(actual, expected);
-
-    let actual: Vec<String> = parse_range("C..A").unwrap().collect();
-    let expected: Vec<String> = vec!["C".to_owned(), "B".to_owned()];
-    assert_eq!(actual, expected);
-
-    let actual: Vec<String> = parse_range("c..a").unwrap().collect();
-    let expected: Vec<String> = vec!["c".to_owned(), "b".to_owned()];
-    assert_eq!(actual, expected);
-
-    let actual: Vec<String> = parse_range("-3..4").unwrap().collect();
-    let expected: Vec<String> = vec![
-        "-3".to_owned(),
-        "-2".to_owned(),
-        "-1".to_owned(),
-        "0".to_owned(),
-        "1".to_owned(),
-        "2".to_owned(),
-        "3".to_owned(),
-    ];
-
-    assert_eq!(actual, expected);
-
-    let actual: Vec<String> = parse_range("3..-4").unwrap().collect();
-    let expected: Vec<String> = vec![
-        "3".to_owned(),
-        "2".to_owned(),
-        "1".to_owned(),
-        "0".to_owned(),
-        "-1".to_owned(),
-        "-2".to_owned(),
-        "-3".to_owned(),
-    ];
-
-    assert_eq!(actual, expected);
-
-    let actual: Vec<String> = parse_range("-3...0").unwrap().collect();
-    let expected: Vec<String> = vec!["-3".into(), "-2".into(), "-1".into(), "0".into()];
-    assert_eq!(actual, expected);
-
-    let actual: Vec<String> = parse_range("-3..0").unwrap().collect();
-    let expected: Vec<String> = vec!["-3".into(), "-2".into(), "-1".into()];
-    assert_eq!(actual, expected);
-}
diff --git a/src/lib/parser/shell_expand/words/range.rs b/ion_ranges/src/range.rs
similarity index 84%
rename from src/lib/parser/shell_expand/words/range.rs
rename to ion_ranges/src/range.rs
index d3c779e5..5bdb9e8c 100644
--- a/src/lib/parser/shell_expand/words/range.rs
+++ b/ion_ranges/src/range.rs
@@ -2,7 +2,7 @@ use super::Index;
 
 /// A range of values in a vector-like object
 #[derive(Debug, PartialEq, Copy, Clone)]
-pub(crate) struct Range {
+pub struct Range {
     /// Starting index
     start: Index,
     /// Ending index
@@ -24,7 +24,7 @@ impl Range {
     /// let selection = vec.iter().skip(start).take(size).collect::<Vec<_>>();
     /// assert_eq!(expected, selection);
     /// ```
-    pub(crate) fn bounds(&self, vector_length: usize) -> Option<(usize, usize)> {
+    pub fn bounds(&self, vector_length: usize) -> Option<(usize, usize)> {
         if let Some(start) = self.start.resolve(vector_length) {
             if let Some(end) = self.end.resolve(vector_length) {
                 if end < start {
@@ -42,7 +42,7 @@ impl Range {
         }
     }
 
-    pub(crate) fn exclusive(start: Index, end: Index) -> Range {
+    pub fn exclusive(start: Index, end: Index) -> Range {
         Range {
             start,
             end,
@@ -50,7 +50,7 @@ impl Range {
         }
     }
 
-    pub(crate) fn inclusive(start: Index, end: Index) -> Range {
+    pub fn inclusive(start: Index, end: Index) -> Range {
         Range {
             start,
             end,
@@ -58,7 +58,7 @@ impl Range {
         }
     }
 
-    pub(crate) fn from(start: Index) -> Range {
+    pub fn from(start: Index) -> Range {
         Range {
             start,
             end: Index::new(-1),
@@ -66,7 +66,7 @@ impl Range {
         }
     }
 
-    pub(crate) fn to(end: Index) -> Range {
+    pub fn to(end: Index) -> Range {
         Range {
             start: Index::new(0),
             end,
diff --git a/src/lib/parser/shell_expand/words/select.rs b/ion_ranges/src/select.rs
similarity index 89%
rename from src/lib/parser/shell_expand/words/select.rs
rename to ion_ranges/src/select.rs
index b25cfa81..2a6bad1b 100644
--- a/src/lib/parser/shell_expand/words/select.rs
+++ b/ion_ranges/src/select.rs
@@ -1,11 +1,12 @@
-use super::{super::ranges::parse_index_range, methods::Key, Index, Range};
+use smallstring::SmallString;
 use std::{
     iter::{empty, FromIterator}, str::FromStr,
 };
+use super::{parse_index_range, Index, Range};
 
 /// Represents a filter on a vector-like object
 #[derive(Debug, PartialEq, Clone)]
-pub(crate) enum Select {
+pub enum Select {
     /// Select no elements
     None,
     /// Select all elements
@@ -15,10 +16,10 @@ pub(crate) enum Select {
     /// Select a range of elements
     Range(Range),
     /// Select an element by mapped key
-    Key(Key),
+    Key(SmallString),
 }
 
-pub(crate) trait SelectWithSize {
+pub trait SelectWithSize {
     type Item;
     fn select<O>(&mut self, Select, usize) -> O
     where
@@ -69,6 +70,6 @@ impl FromStr for Select {
             return Ok(Select::Range(range));
         }
 
-        Ok(Select::Key(Key { key: data.into() }))
+        Ok(Select::Key(data.into()))
     }
 }
diff --git a/ion_sys/src/sys/unix/mod.rs b/ion_sys/src/sys/unix/mod.rs
index 9ee6a27a..df0f0d71 100644
--- a/ion_sys/src/sys/unix/mod.rs
+++ b/ion_sys/src/sys/unix/mod.rs
@@ -53,7 +53,7 @@ pub fn strerror(errno: i32) -> &'static str {
         if ptr.is_null() {
             return "Unknown Error";
         }
-        
+
         CStr::from_ptr(ptr)
             .to_str()
             .unwrap_or("Unknown Error")
@@ -214,7 +214,7 @@ pub fn fork_and_exec<F: Fn(), S: AsRef<str>>(
     }
 }
 
-pub fn execve<'a, S: AsRef<str>>(prog: &str, args: &[S], clear_env: bool) -> io::Error {
+pub fn execve<S: AsRef<str>>(prog: &str, args: &[S], clear_env: bool) -> io::Error {
     let prog_str = match CString::new(prog) {
         Ok(prog) => prog,
         Err(_) => {
diff --git a/src/lib/lib.rs b/src/lib/lib.rs
index 731ea13c..d5d1bf23 100644
--- a/src/lib/lib.rs
+++ b/src/lib/lib.rs
@@ -31,7 +31,9 @@ extern crate unicode_segmentation;
 extern crate xdg;
 
 pub extern crate ion_sys as sys;
+extern crate ion_braces as braces;
 extern crate ion_builtins;
+extern crate ion_ranges as ranges;
 
 #[macro_use]
 mod types;
diff --git a/src/lib/parser/shell_expand/mod.rs b/src/lib/parser/shell_expand/mod.rs
index 9e613025..849b5250 100644
--- a/src/lib/parser/shell_expand/mod.rs
+++ b/src/lib/parser/shell_expand/mod.rs
@@ -1,13 +1,11 @@
 // TODO: Handle Runtime Errors
 extern crate calc;
-extern crate permutate;
 
-mod braces;
-mod ranges;
 mod words;
 
-pub(crate) use self::words::{Index, Range, Select, WordIterator, WordToken};
-use self::{braces::BraceToken, ranges::parse_range};
+pub(crate) use self::words::{Select, WordIterator, WordToken};
+use braces::{self, BraceToken};
+use ranges::{parse_range, Index, Range};
 use glob::glob;
 use std::{ptr, str};
 use types::*;
@@ -209,8 +207,8 @@ pub(crate) fn expand_string<E: Expander>(
                     }
                     WordToken::ArrayVariable(data, contains_quote, selection) => {
                         if let Select::Key(key) = selection {
-                            if key.key.contains(' ') {
-                                for index in key.key.split(' ') {
+                            if key.contains(' ') {
+                                for index in key.split(' ') {
                                     let select = index.parse::<Select>().unwrap_or(Select::None);
                                     token_buffer.push(
                                         WordToken::ArrayVariable(
@@ -374,7 +372,7 @@ fn expand_braces<E: Expander>(
             .map(|list| list.iter().map(AsRef::as_ref).collect::<Vec<&str>>())
             .collect();
         let vector_of_arrays: Vec<&[&str]> = tmp.iter().map(AsRef::as_ref).collect();
-        for word in braces::expand_braces(&tokens, &*vector_of_arrays) {
+        for word in braces::expand(&tokens, &*vector_of_arrays) {
             expanded_words.push(word.into());
         }
     }
@@ -573,7 +571,7 @@ pub(crate) fn expand_tokens<E: Expander>(
                 None => expand_single_string_token(token, expand_func, reverse_quoting),
             };
         }
-    
+
         let mut output = String::new();
         let mut expanded_words = Array::new();
 
diff --git a/src/lib/parser/shell_expand/words/methods/arrays.rs b/src/lib/parser/shell_expand/words/methods/arrays.rs
index 586b8738..0a4d414f 100644
--- a/src/lib/parser/shell_expand/words/methods/arrays.rs
+++ b/src/lib/parser/shell_expand/words/methods/arrays.rs
@@ -1,9 +1,10 @@
 use super::{
     super::{
-        super::{expand_string, is_expression, Expander}, Index, Select, SelectWithSize,
+        super::{expand_string, is_expression, Expander}, Select, SelectWithSize,
     },
     strings::unescape, Pattern,
 };
+use ranges::Index;
 use smallstring::SmallString;
 use std::char;
 use types::Array;
@@ -211,9 +212,8 @@ impl<'a> ArrayMethod<'a> {
 
 #[cfg(test)]
 mod test {
-    use super::{
-        super::{super::Range, Key}, *,
-    };
+    use super::*;
+    use ranges::Range;
     use types::Value;
 
     struct VariableExpander;
@@ -360,7 +360,7 @@ mod test {
             method:    "split",
             variable:  "$SPACEDFOO",
             pattern:   Pattern::Whitespace,
-            selection: Select::Key(Key::new("1")),
+            selection: Select::Key("1".into()),
         };
         method.handle(&mut output, &VariableExpander);
         assert_eq!(output, "");
diff --git a/src/lib/parser/shell_expand/words/methods/mod.rs b/src/lib/parser/shell_expand/words/methods/mod.rs
index a7d20b93..f05ec829 100644
--- a/src/lib/parser/shell_expand/words/methods/mod.rs
+++ b/src/lib/parser/shell_expand/words/methods/mod.rs
@@ -12,18 +12,6 @@ pub(crate) enum Pattern<'a> {
     Whitespace,
 }
 
-#[derive(Debug, PartialEq, Clone)]
-pub(crate) struct Key {
-    pub(crate) key: ::types::Key,
-}
-
-impl Key {
-    pub(crate) fn get(&self) -> &::types::Key { &self.key }
-
-    #[cfg(test)]
-    pub(crate) fn new<K: Into<::types::Key>>(key: K) -> Key { Key { key: key.into() } }
-}
-
 #[derive(Debug)]
 pub(crate) struct MethodArgs<'a, 'b, E: 'b + Expander> {
     args:   &'a str,
diff --git a/src/lib/parser/shell_expand/words/mod.rs b/src/lib/parser/shell_expand/words/mod.rs
index 529e9b8f..73812321 100644
--- a/src/lib/parser/shell_expand/words/mod.rs
+++ b/src/lib/parser/shell_expand/words/mod.rs
@@ -1,18 +1,11 @@
-mod index;
 mod methods;
-mod range;
-mod select;
 #[cfg(test)]
 mod tests;
 #[cfg(test)]
 mod benchmarks;
 
-#[cfg(test)]
-pub(crate) use self::methods::Key;
-pub(crate) use self::{
-    index::Index, methods::{ArrayMethod, Pattern, StringMethod}, range::Range,
-    select::{Select, SelectWithSize},
-};
+pub(crate) use self::methods::{ArrayMethod, Pattern, StringMethod};
+pub use ranges::{Select, SelectWithSize};
 use super::{super::ArgumentSplitter, expand_string, Expander};
 use shell::escape::unescape;
 use std::borrow::Cow;
diff --git a/src/lib/parser/shell_expand/words/tests.rs b/src/lib/parser/shell_expand/words/tests.rs
index ccd7ff9f..89342df2 100644
--- a/src/lib/parser/shell_expand/words/tests.rs
+++ b/src/lib/parser/shell_expand/words/tests.rs
@@ -1,5 +1,6 @@
 use super::*;
 use types::{Array, Value};
+use ranges::{Index, Range};
 
 struct Empty;
 
@@ -14,16 +15,6 @@ fn compare(input: &str, expected: Vec<WordToken>) {
     assert_eq!(expected.len(), correct);
 }
 
-#[test]
-fn ranges() {
-    let range1 = Range::exclusive(Index::new(1), Index::new(5));
-    assert_eq!(Some((1, 4)), range1.bounds(42));
-    assert_eq!(Some((1, 4)), range1.bounds(7));
-    let range2 = Range::inclusive(Index::new(2), Index::new(-4));
-    assert_eq!(Some((2, 5)), range2.bounds(10));
-    assert_eq!(None, range2.bounds(3));
-}
-
 #[test]
 fn string_method() {
     let input = "$join(array, 'pattern') $join(array, 'pattern')";
@@ -125,7 +116,7 @@ fn indexes() {
             Select::Range(Range::inclusive(Index::new(0), Index::new(3))),
         ),
         WordToken::Whitespace(" "),
-        WordToken::ArrayVariable("array", false, Select::Key(Key::new("abc"))),
+        WordToken::ArrayVariable("array", false, Select::Key("abc".into())),
         WordToken::Whitespace(" "),
         WordToken::ArrayVariable("array", false, Select::Range(Range::to(Index::new(3)))),
         WordToken::Whitespace(" "),
@@ -138,11 +129,11 @@ fn indexes() {
 fn string_keys() {
     let input = "@array['key'] @array[key] @array[]";
     let expected = vec![
-        WordToken::ArrayVariable("array", false, Select::Key(Key::new("key"))),
+        WordToken::ArrayVariable("array", false, Select::Key("key".into())),
         WordToken::Whitespace(" "),
-        WordToken::ArrayVariable("array", false, Select::Key(Key::new("key"))),
+        WordToken::ArrayVariable("array", false, Select::Key("key".into())),
         WordToken::Whitespace(" "),
-        WordToken::ArrayVariable("array", false, Select::Key(Key::new(""))),
+        WordToken::ArrayVariable("array", false, Select::Key("".into())),
     ];
     compare(input, expected);
 }
diff --git a/src/lib/shell/mod.rs b/src/lib/shell/mod.rs
index 9c8f6f7f..7937fb71 100644
--- a/src/lib/shell/mod.rs
+++ b/src/lib/shell/mod.rs
@@ -490,7 +490,7 @@ impl<'a> Expander for Shell {
                             .collect::<Array>(),
                     ),
                     Select::Key(ref key) => {
-                        Some(array![map.get(key.get()).unwrap_or(&"".into()).clone()])
+                        Some(array![map.get(key).unwrap_or(&"".into()).clone()])
                     }
                     _ => None,
                 },
-- 
GitLab