ragit_cli/
span.rs

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