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