ragit_cli/
span.rs

1#[derive(Clone, Debug)]
2pub enum Span {
3    Exact(usize),  // including flags and args
4    FirstArg,
5    End,
6    NthArg(usize),  // including args, not including flags
7    Rendered((String, usize, usize)),
8}
9
10impl Span {
11    pub fn render(&self, args: &[String], skip_first_n: usize) -> Self {
12        let mut rendered_args = Vec::with_capacity(args.len());
13        let mut arg_indices = vec![];
14
15        for (index, arg) in args.iter().enumerate() {
16            if !arg.starts_with("--") && index >= skip_first_n {
17                arg_indices.push(index);
18            }
19
20            if arg.contains(" ") || arg.contains("\"") || arg.contains("'") || arg.contains("\n") {
21                rendered_args.push(format!("{arg:?}"));
22            }
23
24            else {
25                rendered_args.push(arg.to_string());
26            }
27        }
28
29        let new_span = match self {
30            Span::Exact(n) => Span::Exact(*n),
31            Span::FirstArg => match arg_indices.get(0) {
32                Some(n) => Span::Exact(*n),
33                None => Span::End,
34            },
35            Span::NthArg(n) => match arg_indices.get(*n) {
36                Some(n) => Span::Exact(*n),
37                None => Span::End,
38            },
39            _ => self.clone(),
40        };
41        let selected_index = match new_span {
42            Span::Exact(n) => n,
43            _ => 0,
44        };
45        let mut joined_args = rendered_args.join(" ");
46        let (start, end) = if joined_args.is_empty() {
47            joined_args = String::from(" ");
48            (0, 1)
49        } else {
50            // append a whitespace so that `Span::End` is more readable
51            joined_args = format!("{joined_args} ");
52
53            match new_span {
54                Span::End => (joined_args.len() - 1, joined_args.len()),
55                _ => (
56                    rendered_args[..selected_index].iter().map(|arg| arg.len()).sum::<usize>() + selected_index,
57                    rendered_args[..(selected_index + 1)].iter().map(|arg| arg.len()).sum::<usize>() + selected_index,
58                ),
59            }
60        };
61
62        Span::Rendered((
63            joined_args,
64            start,
65            end,
66        ))
67    }
68
69    pub fn unwrap_rendered(&self) -> (String, usize, usize) {
70        match self {
71            Span::Rendered((span, start, end)) => (span.to_string(), *start, *end),
72            _ => panic!(),
73        }
74    }
75}
76
77pub fn underline_span(args: &str, start: usize, end: usize) -> String {
78    format!(
79        "{args}\n{}{}{}",
80        " ".repeat(start),
81        "^".repeat(end - start),
82        " ".repeat(args.len() - end),
83    )
84}