greymd / teip

Masking tape to help commands "do one thing well"
MIT License
569 stars 19 forks source link

Extended LIST Syntax #8

Open greymd opened 4 years ago

greymd commented 4 years ago

echo ABCDE | teip -c '1,3,$-1' => [A]B[C][D]E

$-1 supposed to be 4, because $ means the number of field.

Instead of -, .. is used for specifying the range.

1..3 => 1,2,3

greymd commented 4 years ago

just a idea. I want to keep compatibility with cut.

1~3 => same chunk 1-3 => three individual chunks

greymd commented 1 year ago

My new idea is this.

1-5 .. Just select from 1 to 5 (default)
1~5 .. from 1 to 5 are explicitly merged
1:5 .. from 1 to 5 are explicitly separated

Keep - expression to keep backword compatibility. That means, - behavior changes depending on the other consolidated options, which is current behavior. With -c, merged, with -f separated.

On the other hand, ~ can explicitly merge chunks. Such like..

$ echo AAA BBB CCC | teip -f 1~2
[AAA BBB] CCC

: can explicitly separate chunks on the other hand.

$ echo 123456789 | teip -f 2:5
1[2][3][4][5]6789

Syntax parser can be implemented something like that. They are patch for v2.2.0.

diff --git a/src/list/ranges.rs b/src/list/ranges.rs
index 85b20fe..6f71bf5 100644
--- a/src/list/ranges.rs
+++ b/src/list/ranges.rs
@@ -1,10 +1,8 @@
 /*
- * This file is part of the uutils coreutils package.
+ * This file is based on the uutils coreutils package.
  *
- * (c) Rolf Morel <rolfmorel@gmail.com>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
+ * For the full copyright and license information about
+ * the original file, please view the LICENSE
  */

 use std::str::FromStr;
@@ -13,6 +11,17 @@ use std::str::FromStr;
 pub struct Range {
     pub low: usize,
     pub high: usize,
+    pub join: RangeJoin,
+}
+
+#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)]
+pub enum RangeJoin {
+    /// 1-5 .. Just select from 1 to 5 (default)
+    Normal,
+    /// 1~5 .. from 1 to 5 are explicitly merged
+    Merge,
+    /// 1:5 .. from 1 to 5 are explicitly split
+    Split,
 }

 impl FromStr for Range {
@@ -20,8 +29,20 @@ impl FromStr for Range {

     fn from_str(s: &str) -> Result<Range, &'static str> {
         use std::usize::MAX;
-
-        let mut parts = s.splitn(2, '-');
+        let join: RangeJoin;
+
+        // check if s includes a ~ or a - and split on that
+        // if not, assume it's a single number
+        let mut parts = if s.contains('~') {
+            join = RangeJoin::Merge;
+            s.splitn(2, '~')
+        } else if s.contains(':') {
+            join = RangeJoin::Split;
+            s.splitn(2, ':')
+        } else {
+            join = RangeJoin::Normal;
+            s.splitn(2, '-')
+        };

         let field = "fields and positions are numbered from 1";
         let order = "high end of range less than low end";
@@ -31,7 +52,7 @@ impl FromStr for Range {
             (Some(nm), None) => {
                 if let Ok(nm) = nm.parse::<usize>() {
                     if nm > 0 {
-                        Ok(Range { low: nm, high: nm })
+                        Ok(Range { low: nm, high: nm, join: RangeJoin::Normal })
                     } else {
                         Err(field)
                     }
@@ -42,7 +63,7 @@ impl FromStr for Range {
             (Some(n), Some(m)) if m.is_empty() => {
                 if let Ok(low) = n.parse::<usize>() {
                     if low > 0 {
-                        Ok(Range { low, high: MAX - 1 })
+                        Ok(Range { low, high: MAX - 1, join })
                     } else {
                         Err(field)
                     }
@@ -53,7 +74,7 @@ impl FromStr for Range {
             (Some(n), Some(m)) if n.is_empty() => {
                 if let Ok(high) = m.parse::<usize>() {
                     if high > 0 {
-                        Ok(Range { low: 1, high })
+                        Ok(Range { low: 1, high, join })
                     } else {
                         Err(field)
                     }
@@ -64,7 +85,7 @@ impl FromStr for Range {
             (Some(n), Some(m)) => match (n.parse::<usize>(), m.parse::<usize>()) {
                 (Ok(low), Ok(high)) => {
                     if low > 0 && low <= high {
-                        Ok(Range { low, high })
+                        Ok(Range { low, high, join })
                     } else if low == 0 {
                         Err(field)
                     } else {
@@ -111,11 +132,14 @@ pub fn complement(ranges: &[Range]) -> Vec<Range> {
     use std::usize;

     let mut complements = Vec::with_capacity(ranges.len() + 1);
+    // Use the default join type to keep back compatibility
+    const DEF_JOIN: RangeJoin = RangeJoin::Normal;

     if !ranges.is_empty() && ranges[0].low > 1 {
         complements.push(Range {
             low: 1,
             high: ranges[0].low - 1,
+            join: DEF_JOIN,
         });
     }

@@ -127,6 +151,7 @@ pub fn complement(ranges: &[Range]) -> Vec<Range> {
                     complements.push(Range {
                         low: left.high + 1,
                         high: right.low - 1,
+                        join: DEF_JOIN,
                     });
                 }
             }
@@ -135,6 +160,7 @@ pub fn complement(ranges: &[Range]) -> Vec<Range> {
                     complements.push(Range {
                         low: last.high + 1,
                         high: usize::MAX - 1,
+                        join: DEF_JOIN,
                     });
                 }
             }
diff --git a/src/list/converter.rs b/src/list/converter.rs
index b2841c1..5e17b1a 100644
--- a/src/list/converter.rs
+++ b/src/list/converter.rs
@@ -11,10 +11,80 @@ pub fn to_ranges(list: &str, complement: bool) -> Result<Vec<Range>, String> {
 #[cfg(test)]
 mod test {
     use super::*;
+    use ranges::RangeJoin::{Merge, Normal, Split};
     #[test]
     fn test_to_ranges() {
         let range = to_ranges("2-5,1-8", false).unwrap();
         assert_eq!(range[0].low, 1);
         assert_eq!(range[0].high, 8);
     }
+    #[test]
+    fn test_to_ranges_merge_unmerge() {
+        let range = to_ranges("1-3,4~6", false).unwrap();
+        assert_eq!(range[0].low, 1);
+        assert_eq!(range[0].high, 3);
+        assert_eq!(range[0].join, Normal);
+        assert_eq!(range[1].low, 4);
+        assert_eq!(range[1].high, 6);
+        assert_eq!(range[1].join, Merge);
+    }
+    #[test]
+    fn test_to_ranges_unsort() {
+        let range = to_ranges("4,1-3,5~7", false).unwrap();
+        println!("{:?}", range);
+        assert_eq!(range[0].low, 1);
+        assert_eq!(range[0].high, 3);
+        assert_eq!(range[0].join, Normal);
+        assert_eq!(range[1].low, 4);
+        assert_eq!(range[1].high, 4);
+        assert_eq!(range[1].join, Normal);
+        assert_eq!(range[2].low, 5);
+        assert_eq!(range[2].high, 7);
+        assert_eq!(range[2].join, Merge);
+    }
+    #[test]
+    fn test_to_ranges_split() {
+        let range = to_ranges("1,2,3,4", false).unwrap();
+        println!("{:?}", range);
+        for i in 0..4 {
+            assert_eq!(range[i].low, i + 1);
+            assert_eq!(range[i].high, i + 1);
+            assert_eq!(range[i].join, Normal);
+        }
+    }
+    #[test]
+    fn test_to_ranges_split_unsort() {
+        let range = to_ranges("5,3,4,1,2", false).unwrap();
+        println!("{:?}", range);
+        for i in 0..5 {
+            assert_eq!(range[i].low, i + 1);
+            assert_eq!(range[i].high, i + 1);
+            assert_eq!(range[i].join, Normal);
+        }
+    }
+    #[test]
+    fn test_to_ranges_overwrap() {
+        let range = to_ranges("1-3,2~5", false).unwrap();
+        println!("{:?}", range);
+        assert_eq!(range[0].low, 1);
+        assert_eq!(range[0].high, 5);
+        assert_eq!(range[0].join, Normal);
+    }
+    #[test]
+    fn test_to_ranges_three_different_range() {
+        let range = to_ranges("1-3,5:10,12,13~15", false).unwrap();
+        println!("{:?}", range);
+        assert_eq!(range[0].low, 1);
+        assert_eq!(range[0].high, 3);
+        assert_eq!(range[0].join, Normal);
+        assert_eq!(range[1].low, 5);
+        assert_eq!(range[1].high, 10);
+        assert_eq!(range[1].join, Split);
+        assert_eq!(range[2].low, 12);
+        assert_eq!(range[2].high, 12);
+        assert_eq!(range[2].join, Normal);
+        assert_eq!(range[3].low, 13);
+        assert_eq!(range[3].high, 15);
+        assert_eq!(range[3].join, Merge);
+    }
 }