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