ru_shell 0.1.0

A Shell built around a semantic grammar
use bogobble::traits::CharBool;
use std::slice::SliceIndex;

impl CharStr for str {
    fn get_i(&self, n: usize) -> Option<&str> {
        self.get(n..)
    }
    fn get_r<I: SliceIndex<str>>(&self, i: I) -> Option<&<I as SliceIndex<str>>::Output> {
        self.get(i)
    }
}

pub trait CharStr {
    fn get_i(&self, n: usize) -> Option<&str>;
    fn get_r<I: SliceIndex<str>>(&self, i: I) -> Option<&<I as SliceIndex<str>>::Output>;

    fn char_left(&self, mut n: usize) -> Option<usize> {
        while n > 0 {
            n -= 1;
            if self.get_i(n).is_some() {
                return Some(n);
            }
        }
        None
    }

    fn count_between(&self, a: usize, b: usize) -> Option<usize> {
        Some(self.get_r(a..b)?.chars().fold(0, |a, _| a + 1))
    }

    fn char_right_n_match<C: CharBool>(&self, n: usize, d: usize, pat: C) -> Option<usize> {
        let mut it = self.get_i(n)?.char_indices();
        it.next();
        for _ in 1..d {
            match it.next() {
                Some((i, c)) if pat.char_bool(c) => return Some(i + n),
                _ => {}
            }
        }
        it.next().map(|(i, _)| i + n)
    }

    fn char_right(&self, n: usize) -> Option<usize> {
        self.char_right_n_match(n, 1, "")
    }

    fn char_at(&self, n: usize) -> Option<char> {
        self.get_i(n).and_then(|n| n.chars().next())
    }

    fn prev_match<C: CharBool>(&self, target: C, mut n: usize) -> Option<usize> {
        while n > 0 {
            n -= 1;
            match self.char_at(n) {
                Some(c) if target.char_bool(c) => return Some(n),
                _ => {}
            }
        }
        None
    }

    fn next_match<C: CharBool>(&self, target: C, n: usize) -> Option<usize> {
        for (i, c) in self.get_i(n)?.char_indices() {
            if target.char_bool(c) {
                return Some(n + i);
            }
        }
        None
    }
}
#[cfg(test)]
mod test {
    use super::*;
    #[test]
    fn read_length_between_points() {
        assert_eq!("hello world".count_between(2, 4), Some(2));
        assert_eq!("hello 中国and others".count_between(6, 12), Some(2));
    }

    #[test]
    fn step_right_to_line() {
        assert_eq!("hello world".char_right_n_match(2, 2, '\n'), Some(4));
        assert_eq!("hello\nworld".char_right_n_match(2, 10, '\n'), Some(5));
    }

    #[test]
    fn previous_match() {
        assert_eq!("hello world".prev_match(' ', 8), Some(5));
        assert_eq!("he lo world".prev_match(' ', 5), Some(2));
        assert_eq!("he l  world".prev_match(' ', 5), Some(4));
        assert_eq!("hello world".prev_match(' ', 5), None);
    }
}