lib.rs 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  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. "p" | "x" => DisplayHint::LowerHex,
  50. "X" => DisplayHint::UpperHex,
  51. "i" => DisplayHint::Ip,
  52. "mac" => DisplayHint::LowerMac,
  53. "MAC" => DisplayHint::UpperMac,
  54. _ => return Err(format!("unknown display hint: {s:?}")),
  55. })
  56. }
  57. /// Parse `Param` from the given `&str` which can specify an optional format
  58. /// like `:x` or `:ipv4` (without curly braces, which are parsed by the `parse`
  59. /// function).
  60. fn parse_param(mut input: &str) -> Result<Parameter, String> {
  61. const HINT_PREFIX: &str = ":";
  62. // Then, optional hint
  63. let mut hint = DisplayHint::Default;
  64. if input.starts_with(HINT_PREFIX) {
  65. // skip the prefix
  66. input = &input[HINT_PREFIX.len()..];
  67. if input.is_empty() {
  68. return Err("malformed format string (missing display hint after ':')".into());
  69. }
  70. hint = parse_display_hint(input)?;
  71. } else if !input.is_empty() {
  72. return Err(format!("unexpected content {input:?} in format string"));
  73. }
  74. Ok(Parameter { hint })
  75. }
  76. /// Parses the given format string into string literals and parameters specified
  77. /// by curly braces (with optional format hints like `:x` or `:ipv4`).
  78. pub fn parse(format_string: &str) -> Result<Vec<Fragment>, String> {
  79. let mut fragments = Vec::new();
  80. // Index after the `}` of the last format specifier.
  81. let mut end_pos = 0;
  82. let mut chars = format_string.char_indices();
  83. while let Some((brace_pos, ch)) = chars.next() {
  84. if ch != '{' {
  85. // Part of a literal fragment.
  86. continue;
  87. }
  88. // Peek at the next char.
  89. if chars.as_str().starts_with('{') {
  90. // Escaped `{{`, also part of a literal fragment.
  91. chars.next();
  92. continue;
  93. }
  94. if brace_pos > end_pos {
  95. // There's a literal fragment with at least 1 character before this
  96. // parameter fragment.
  97. let unescaped_literal = &format_string[end_pos..brace_pos];
  98. push_literal(&mut fragments, unescaped_literal)?;
  99. }
  100. // Else, this is a format specifier. It ends at the next `}`.
  101. let len = chars
  102. .as_str()
  103. .find('}')
  104. .ok_or("missing `}` in format string")?;
  105. end_pos = brace_pos + 1 + len + 1;
  106. // Parse the contents inside the braces.
  107. let param_str = &format_string[brace_pos + 1..][..len];
  108. let param = parse_param(param_str)?;
  109. fragments.push(Fragment::Parameter(param));
  110. }
  111. // Trailing literal.
  112. if end_pos != format_string.len() {
  113. push_literal(&mut fragments, &format_string[end_pos..])?;
  114. }
  115. Ok(fragments)
  116. }
  117. #[cfg(test)]
  118. mod test {
  119. use super::*;
  120. #[test]
  121. fn test_parse() {
  122. assert_eq!(
  123. parse("foo {} bar {:x} test {:X} ayy {:i} lmao {{}} {{something}} {:p}"),
  124. Ok(vec![
  125. Fragment::Literal("foo ".into()),
  126. Fragment::Parameter(Parameter {
  127. hint: DisplayHint::Default
  128. }),
  129. Fragment::Literal(" bar ".into()),
  130. Fragment::Parameter(Parameter {
  131. hint: DisplayHint::LowerHex
  132. }),
  133. Fragment::Literal(" test ".into()),
  134. Fragment::Parameter(Parameter {
  135. hint: DisplayHint::UpperHex
  136. }),
  137. Fragment::Literal(" ayy ".into()),
  138. Fragment::Parameter(Parameter {
  139. hint: DisplayHint::Ip
  140. }),
  141. Fragment::Literal(" lmao {} {something} ".into()),
  142. Fragment::Parameter(Parameter {
  143. hint: DisplayHint::LowerHex
  144. }),
  145. ])
  146. );
  147. assert!(parse("foo {:}").is_err());
  148. assert!(parse("foo { bar").is_err());
  149. assert!(parse("foo } bar").is_err());
  150. assert!(parse("foo { bar }").is_err());
  151. }
  152. }