lib.rs 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. use std::str;
  2. use aya_log_common::DisplayHint;
  3. /// A parsed formatting parameter (contents of `{` `}` block).
  4. #[derive(Clone, Debug, PartialEq, Eq)]
  5. pub struct Parameter {
  6. /// The display hint, e.g. ':ipv4', ':x'.
  7. pub hint: DisplayHint,
  8. }
  9. #[derive(Clone, Debug, PartialEq, Eq)]
  10. pub enum Fragment {
  11. /// A literal string (eg. `"literal "` in `"literal {}"`).
  12. Literal(String),
  13. /// A format parameter.
  14. Parameter(Parameter),
  15. }
  16. fn push_literal(frag: &mut Vec<Fragment>, unescaped_literal: &str) -> Result<(), String> {
  17. // Replace `{{` with `{` and `}}` with `}`. Single braces are errors.
  18. // Scan for single braces first. The rest is trivial.
  19. let mut last_open = false;
  20. let mut last_close = false;
  21. for c in unescaped_literal.chars() {
  22. match c {
  23. '{' => last_open = !last_open,
  24. '}' => last_close = !last_close,
  25. _ => {
  26. if last_open {
  27. return Err("unmatched `{` in format string".into());
  28. }
  29. if last_close {
  30. return Err("unmatched `}` in format string".into());
  31. }
  32. }
  33. }
  34. }
  35. // Handle trailing unescaped `{` or `}`.
  36. if last_open {
  37. return Err("unmatched `{` in format string".into());
  38. }
  39. if last_close {
  40. return Err("unmatched `}` in format string".into());
  41. }
  42. let literal = unescaped_literal.replace("{{", "{").replace("}}", "}");
  43. frag.push(Fragment::Literal(literal));
  44. Ok(())
  45. }
  46. /// Parses the display hint (e.g. the `ipv4` in `{:ipv4}`).
  47. fn parse_display_hint(s: &str) -> Result<DisplayHint, String> {
  48. Ok(match s {
  49. "x" => DisplayHint::LowerHex,
  50. "X" => DisplayHint::UpperHex,
  51. "ipv4" => DisplayHint::Ipv4,
  52. "ipv6" => DisplayHint::Ipv6,
  53. "mac" => DisplayHint::LowerMac,
  54. "MAC" => DisplayHint::UpperMac,
  55. _ => return Err(format!("unknown display hint: {s:?}")),
  56. })
  57. }
  58. /// Parse `Param` from the given `&str` which can specify an optional format
  59. /// like `:x` or `:ipv4` (without curly braces, which are parsed by the `parse`
  60. /// function).
  61. fn parse_param(mut input: &str) -> Result<Parameter, String> {
  62. const HINT_PREFIX: &str = ":";
  63. // Then, optional hint
  64. let mut hint = DisplayHint::Default;
  65. if input.starts_with(HINT_PREFIX) {
  66. // skip the prefix
  67. input = &input[HINT_PREFIX.len()..];
  68. if input.is_empty() {
  69. return Err("malformed format string (missing display hint after ':')".into());
  70. }
  71. hint = parse_display_hint(input)?;
  72. } else if !input.is_empty() {
  73. return Err(format!("unexpected content {input:?} in format string"));
  74. }
  75. Ok(Parameter { hint })
  76. }
  77. /// Parses the given format string into string literals and parameters specified
  78. /// by curly braces (with optional format hints like `:x` or `:ipv4`).
  79. pub fn parse(format_string: &str) -> Result<Vec<Fragment>, String> {
  80. let mut fragments = Vec::new();
  81. // Index after the `}` of the last format specifier.
  82. let mut end_pos = 0;
  83. let mut chars = format_string.char_indices();
  84. while let Some((brace_pos, ch)) = chars.next() {
  85. if ch != '{' {
  86. // Part of a literal fragment.
  87. continue;
  88. }
  89. // Peek at the next char.
  90. if chars.as_str().starts_with('{') {
  91. // Escaped `{{`, also part of a literal fragment.
  92. chars.next();
  93. continue;
  94. }
  95. if brace_pos > end_pos {
  96. // There's a literal fragment with at least 1 character before this
  97. // parameter fragment.
  98. let unescaped_literal = &format_string[end_pos..brace_pos];
  99. push_literal(&mut fragments, unescaped_literal)?;
  100. }
  101. // Else, this is a format specifier. It ends at the next `}`.
  102. let len = chars
  103. .as_str()
  104. .find('}')
  105. .ok_or("missing `}` in format string")?;
  106. end_pos = brace_pos + 1 + len + 1;
  107. // Parse the contents inside the braces.
  108. let param_str = &format_string[brace_pos + 1..][..len];
  109. let param = parse_param(param_str)?;
  110. fragments.push(Fragment::Parameter(param));
  111. }
  112. // Trailing literal.
  113. if end_pos != format_string.len() {
  114. push_literal(&mut fragments, &format_string[end_pos..])?;
  115. }
  116. Ok(fragments)
  117. }
  118. #[cfg(test)]
  119. mod test {
  120. use super::*;
  121. #[test]
  122. fn test_parse() {
  123. assert_eq!(
  124. parse("foo {} bar {:x} test {:X} ayy {:ipv4} lmao {:ipv6} {{}} {{something}}"),
  125. Ok(vec![
  126. Fragment::Literal("foo ".into()),
  127. Fragment::Parameter(Parameter {
  128. hint: DisplayHint::Default
  129. }),
  130. Fragment::Literal(" bar ".into()),
  131. Fragment::Parameter(Parameter {
  132. hint: DisplayHint::LowerHex
  133. }),
  134. Fragment::Literal(" test ".into()),
  135. Fragment::Parameter(Parameter {
  136. hint: DisplayHint::UpperHex
  137. }),
  138. Fragment::Literal(" ayy ".into()),
  139. Fragment::Parameter(Parameter {
  140. hint: DisplayHint::Ipv4
  141. }),
  142. Fragment::Literal(" lmao ".into()),
  143. Fragment::Parameter(Parameter {
  144. hint: DisplayHint::Ipv6
  145. }),
  146. Fragment::Literal(" {{}} {{something}}".into()),
  147. ])
  148. );
  149. assert!(parse("foo {:}").is_err());
  150. assert!(parse("foo { bar").is_err());
  151. assert!(parse("foo } bar").is_err());
  152. assert!(parse("foo { bar }").is_err());
  153. }
  154. }