1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
//! Parses inline permissions outside of an Aquascope block.

use anyhow::{bail, Result};
use regex::Regex;

const VALID_PERMISSIONS: &[&str] = &["read", "write", "own", "flow"];

pub type Replacement = (std::ops::Range<usize>, String);

pub fn parse_perms(
  content: &'_ str,
) -> impl Iterator<Item = Result<Replacement>> + '_ {
  lazy_static::lazy_static! {
      static ref RE: Regex = Regex::new(r"@Perm(\[[^\]]*\])?\{([^}]*)\}").unwrap();
  }

  RE.captures_iter(content)
    .map(move |cap| {
      let range = cap.get(0).unwrap().range();
      let perm = cap.get(2).unwrap().as_str();
      if !VALID_PERMISSIONS.contains(&perm) {
          bail!("Invalid permission: {perm}");
      }

      let letter = perm.chars().next().unwrap().to_ascii_uppercase();
      let perm_html = format!(r#"<span class="perm {perm}">{letter}</span>"#);
      let html = match cap.get(1) {
        Some(options) => match options.as_str() {
          "[gained]" => format!(
            r#"<span><span class="perm-diff-add">+</span>{perm_html}</span>"#
          ),
          "[lost]" => format!(
            r#"<span class="perm-diff-sub-container"><div class="perm-diff-sub"></div>{perm_html}</span>"#
          ),
          "[missing]" => format!(r#"<span class="perm missing {perm}">{letter}</span>"#),
          _ => unimplemented!(),
        },
        None => perm_html,
      };
      Ok((range, html))
    })
}

#[cfg(test)]
mod test {
  use super::*;
  fn parse(s: &str) -> Vec<Replacement> {
    parse_perms(s).collect::<Result<Vec<_>>>().unwrap()
  }

  #[test]
  fn test_parse_perms() {
    let s = "Hello @Perm{read} world";
    assert_eq!(parse(s), vec![
      ((6 .. 17, String::from(r#"<span class="perm read">R</span>"#)))
    ]);

    let s = "Hello @Perm[lost]{read} world";
    assert_eq!(parse(s), vec![
      ((
        6 .. 23,
        String::from(
          r#"<span class="perm-diff-sub-container"><div class="perm-diff-sub"></div><span class="perm read">R</span></span>"#
        )
      ))
    ]);
  }

  #[test]
  #[should_panic]
  fn test_parse_perms_fail() {
    let s = "@Perm{not-a-perm}";
    parse(s);
  }
}