Skip to main content

linsel/
lib.rs

1// SPDX-FileCopyrightText: 2026 David Zaslavsky <diazona@ellipsix.net>
2//
3// SPDX-License-Identifier: GPL-3.0-or-later
4
5mod range;
6
7use std::{io, iter};
8
9pub use range::{CreateRangeError, ParseRangeError, Range, RangeElement};
10
11#[derive(Debug, PartialEq)]
12pub enum ParseArgumentsError {
13    Help,
14    CreateRangeError(range::CreateRangeError),
15    ParseRangeError(range::ParseRangeError),
16}
17
18impl From<range::ParseRangeError> for ParseArgumentsError {
19    fn from(error: range::ParseRangeError) -> Self {
20        ParseArgumentsError::ParseRangeError(error)
21    }
22}
23
24impl From<range::CreateRangeError> for ParseArgumentsError {
25    fn from(error: range::CreateRangeError) -> Self {
26        ParseArgumentsError::CreateRangeError(error)
27    }
28}
29
30pub fn parse_arguments<T: AsRef<str>>(args: &[T]) -> Result<Vec<Range>, ParseArgumentsError> {
31    if args.iter().any(|s| s.as_ref() == "-h" || s.as_ref() == "--help") {
32        Err(ParseArgumentsError::Help)
33    }
34    else {
35        args.iter()
36            .flat_map(|arg| arg.as_ref().split(","))
37            .map(|r| r.parse::<Range>().map_err(ParseArgumentsError::ParseRangeError))
38            .collect()
39    }
40}
41
42#[cfg(test)]
43mod tests {
44    use super::{ParseArgumentsError, Range, parse_arguments};
45
46    #[test]
47    fn no_arguments() {
48        let result = parse_arguments(&Vec::<&str>::new()).unwrap();
49        assert_eq!(result, Vec::<Range>::new());
50    }
51
52    #[test]
53    fn single_number() {
54        let result = parse_arguments(&["1"]).unwrap();
55        let expected = vec![Range::new(1, 1, 1).unwrap()];
56        assert_eq!(result, expected);
57    }
58
59    #[test]
60    fn single_range() {
61        let result = parse_arguments(&["1-10"]).unwrap();
62        assert_eq!(result, vec![Range::new(1, 10, 1).unwrap()]);
63    }
64
65    #[test]
66    fn single_range_with_step() {
67        let result = parse_arguments(&["1-10/2"]).unwrap();
68        assert_eq!(result, vec![Range::new(1, 10, 2).unwrap()]);
69    }
70
71    #[test]
72    fn single_arg_multiple_ranges_with_steps() {
73        let result = parse_arguments(&["1-10/2,12-25/3"]).unwrap();
74        assert_eq!(result, vec![Range::new(1, 10, 2).unwrap(), Range::new(12, 25, 3).unwrap()]);
75    }
76
77    #[test]
78    fn multiple_args() {
79        let result = parse_arguments(&["1-10/2", "12-25/3"]).unwrap();
80        assert_eq!(result, vec![Range::new(1, 10, 2).unwrap(), Range::new(12, 25, 3).unwrap()]);
81    }
82
83    #[test]
84    fn multiple_args_multiple_ranges() {
85        let result = parse_arguments(&["1-10/2,12-25/3", "30-40/5"]).unwrap();
86        assert_eq!(
87            result,
88            vec![
89                Range::new(1, 10, 2).unwrap(),
90                Range::new(12, 25, 3).unwrap(),
91                Range::new(30, 40, 5).unwrap()
92            ]
93        );
94    }
95
96    #[test]
97    fn short_help() {
98        let result = parse_arguments(&["-h"]);
99        assert_eq!(result.unwrap_err(), ParseArgumentsError::Help);
100    }
101
102    #[test]
103    fn long_help() {
104        let result = parse_arguments(&["--help"]);
105        assert_eq!(result.unwrap_err(), ParseArgumentsError::Help);
106    }
107
108    #[test]
109    fn long_help_with_other_args() {
110        let result = parse_arguments(&["1-10/2", "--help"]);
111        assert_eq!(result.unwrap_err(), ParseArgumentsError::Help);
112    }
113}
114
115pub fn print_usage(program: &str) {
116    eprintln!("Usage: {program} <range>[,<range>]...");
117    eprintln!("Read standard input and print the lines selected by the given ranges.");
118    eprintln!();
119    eprintln!("      --help     display this help and exit");
120}
121
122pub fn select_elements<T>(
123    source: impl iter::Iterator<Item = T>,
124    ranges: &[Range],
125) -> impl iter::Iterator<Item = T> {
126    source.enumerate().filter_map(|(i, elem)| {
127        if ranges.iter().any(|r| r.contains(i + 1)) {
128            Some(elem)
129        }
130        else {
131            None
132        }
133    })
134}
135
136pub fn print_selected_lines(ranges: &[Range]) {
137    select_elements(io::stdin().lines(), ranges).for_each(|line| println!("{}", line.unwrap()))
138}