Skip to content

Commit 97a918d

Browse files
committed
decoder: add Frame::fragments() and Frame::display_fragments()
1 parent 6f575b7 commit 97a918d

File tree

3 files changed

+216
-95
lines changed

3 files changed

+216
-95
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -589,6 +589,7 @@ Initial release
589589
### [defmt-decoder-next]
590590

591591
* [#958] Update to object 0.36
592+
* [#966] Add Frame::fragments() and Frame::display_fragments()
592593

593594
### [defmt-decoder-v1.0.0] (2025-04-01)
594595

@@ -947,6 +948,7 @@ Initial release
947948

948949
---
949950

951+
[#966]: https://github.com/knurling-rs/defmt/pull/966
950952
[#965]: https://github.com/knurling-rs/defmt/pull/965
951953
[#960]: https://github.com/knurling-rs/defmt/pull/960
952954
[#959]: https://github.com/knurling-rs/defmt/pull/959

decoder/src/frame.rs

Lines changed: 153 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,46 @@ impl<'t> Frame<'t> {
9191
DisplayMessage { frame: self }
9292
}
9393

94+
/// Returns an iterator over the fragments of the message contained in this log frame.
95+
///
96+
/// Collecting this into a String will yield the same result as [`Self::display_message`], but
97+
/// this iterator will yield interpolated fragments on their own. For example, the log:
98+
///
99+
/// ```ignore
100+
/// defmt::info!("foo = {}, bar = {}", 1, 2);
101+
/// ```
102+
///
103+
/// Will yield the following strings:
104+
///
105+
/// ```
106+
/// vec!["foo = ", "1", ", bar = ", "2"]
107+
/// ```
108+
///
109+
/// Note that nested fragments will not yield separately:
110+
///
111+
/// ```ignore
112+
/// defmt::info!("foo = {}", Foo { bar: 1 });
113+
/// ```
114+
///
115+
/// Will yield:
116+
///
117+
/// ```
118+
/// vec!["foo = ", "Foo { bar: 1 }"]
119+
/// ```
120+
///
121+
/// This iterator yields the same fragments as [`Self::fragments`], so you can zip them
122+
/// together to get both representations.
123+
pub fn display_fragments(&'t self) -> DisplayFragments<'t> {
124+
DisplayFragments {
125+
frame: self,
126+
iter: self.fragments().into_iter(),
127+
}
128+
}
129+
130+
pub fn fragments(&'t self) -> Vec<Fragment<'t>> {
131+
defmt_parser::parse(self.format, ParserMode::ForwardsCompatible).unwrap()
132+
}
133+
94134
pub fn level(&self) -> Option<Level> {
95135
self.level
96136
}
@@ -100,119 +140,120 @@ impl<'t> Frame<'t> {
100140
}
101141

102142
fn format_args(&self, format: &str, args: &[Arg], parent_hint: Option<&DisplayHint>) -> String {
103-
self.format_args_real(format, args, parent_hint).unwrap() // cannot fail, we only write to a `String`
143+
let params = defmt_parser::parse(format, ParserMode::ForwardsCompatible).unwrap();
144+
let mut buf = String::new();
145+
for param in params {
146+
self.format_fragment(param, &mut buf, args, parent_hint)
147+
.unwrap(); // cannot fail, we only write to a `String`
148+
}
149+
buf
104150
}
105151

106-
fn format_args_real(
152+
fn format_fragment(
107153
&self,
108-
format: &str,
154+
param: Fragment<'_>,
155+
buf: &mut String,
109156
args: &[Arg],
110157
parent_hint: Option<&DisplayHint>,
111-
) -> Result<String, fmt::Error> {
112-
let params = defmt_parser::parse(format, ParserMode::ForwardsCompatible).unwrap();
113-
let mut buf = String::new();
114-
for param in params {
115-
match param {
116-
Fragment::Literal(lit) => {
117-
buf.push_str(&lit);
118-
}
119-
Fragment::Parameter(param) => {
120-
let hint = param.hint.as_ref().or(parent_hint);
121-
122-
match &args[param.index] {
123-
Arg::Bool(x) => write!(buf, "{x}")?,
124-
Arg::F32(x) => write!(buf, "{}", ryu::Buffer::new().format(*x))?,
125-
Arg::F64(x) => write!(buf, "{}", ryu::Buffer::new().format(*x))?,
126-
Arg::Uxx(x) => {
127-
match param.ty {
128-
Type::BitField(range) => {
129-
let left_zeroes =
130-
mem::size_of::<u128>() * 8 - range.end as usize;
131-
let right_zeroes = left_zeroes + range.start as usize;
132-
// isolate the desired bitfields
133-
let bitfields = (*x << left_zeroes) >> right_zeroes;
134-
135-
if let Some(DisplayHint::Ascii) = hint {
136-
let bstr = bitfields
137-
.to_be_bytes()
138-
.iter()
139-
.skip(right_zeroes / 8)
140-
.copied()
141-
.collect::<Vec<u8>>();
142-
self.format_bytes(&bstr, hint, &mut buf)?
143-
} else {
144-
self.format_u128(bitfields, hint, &mut buf)?;
145-
}
158+
) -> Result<(), fmt::Error> {
159+
match param {
160+
Fragment::Literal(lit) => {
161+
buf.push_str(&lit);
162+
}
163+
Fragment::Parameter(param) => {
164+
let hint = param.hint.as_ref().or(parent_hint);
165+
166+
match &args[param.index] {
167+
Arg::Bool(x) => write!(buf, "{x}")?,
168+
Arg::F32(x) => write!(buf, "{}", ryu::Buffer::new().format(*x))?,
169+
Arg::F64(x) => write!(buf, "{}", ryu::Buffer::new().format(*x))?,
170+
Arg::Uxx(x) => {
171+
match param.ty {
172+
Type::BitField(range) => {
173+
let left_zeroes = mem::size_of::<u128>() * 8 - range.end as usize;
174+
let right_zeroes = left_zeroes + range.start as usize;
175+
// isolate the desired bitfields
176+
let bitfields = (*x << left_zeroes) >> right_zeroes;
177+
178+
if let Some(DisplayHint::Ascii) = hint {
179+
let bstr = bitfields
180+
.to_be_bytes()
181+
.iter()
182+
.skip(right_zeroes / 8)
183+
.copied()
184+
.collect::<Vec<u8>>();
185+
self.format_bytes(&bstr, hint, buf)?
186+
} else {
187+
self.format_u128(bitfields, hint, buf)?;
146188
}
147-
_ => match hint {
148-
Some(DisplayHint::ISO8601(precision)) => {
149-
self.format_iso8601(*x as u64, precision, &mut buf)?
150-
}
151-
Some(DisplayHint::Debug) => {
152-
self.format_u128(*x, parent_hint, &mut buf)?
153-
}
154-
_ => self.format_u128(*x, hint, &mut buf)?,
155-
},
156189
}
190+
_ => match hint {
191+
Some(DisplayHint::ISO8601(precision)) => {
192+
self.format_iso8601(*x as u64, precision, buf)?
193+
}
194+
Some(DisplayHint::Debug) => {
195+
self.format_u128(*x, parent_hint, buf)?
196+
}
197+
_ => self.format_u128(*x, hint, buf)?,
198+
},
157199
}
158-
Arg::Ixx(x) => self.format_i128(*x, param.ty, hint, &mut buf)?,
159-
Arg::Str(x) | Arg::Preformatted(x) => self.format_str(x, hint, &mut buf)?,
160-
Arg::IStr(x) => self.format_str(x, hint, &mut buf)?,
161-
Arg::Format { format, args } => match parent_hint {
162-
Some(DisplayHint::Ascii) => {
163-
buf.push_str(&self.format_args(format, args, parent_hint));
164-
}
165-
_ => buf.push_str(&self.format_args(format, args, hint)),
166-
},
167-
Arg::FormatSequence { args } => {
168-
for arg in args {
169-
buf.push_str(&self.format_args("{=?}", &[arg.clone()], hint))
170-
}
200+
}
201+
Arg::Ixx(x) => self.format_i128(*x, param.ty, hint, buf)?,
202+
Arg::Str(x) | Arg::Preformatted(x) => self.format_str(x, hint, buf)?,
203+
Arg::IStr(x) => self.format_str(x, hint, buf)?,
204+
Arg::Format { format, args } => match parent_hint {
205+
Some(DisplayHint::Ascii) => {
206+
buf.push_str(&self.format_args(format, args, parent_hint));
171207
}
172-
Arg::FormatSlice { elements } => {
173-
match hint {
174-
// Filter Ascii Hints, which contains u8 byte slices
175-
Some(DisplayHint::Ascii)
176-
if elements.iter().filter(|e| e.format == "{=u8}").count()
177-
!= 0 =>
178-
{
179-
let vals = elements
180-
.iter()
181-
.map(|e| match e.args.as_slice() {
182-
[Arg::Uxx(v)] => u8::try_from(*v)
183-
.expect("the value must be in u8 range"),
184-
_ => panic!(
185-
"FormatSlice should only contain one argument"
186-
),
187-
})
188-
.collect::<Vec<u8>>();
189-
self.format_bytes(&vals, hint, &mut buf)?
190-
}
191-
_ => {
192-
buf.write_str("[")?;
193-
let mut is_first = true;
194-
for element in elements {
195-
if !is_first {
196-
buf.write_str(", ")?;
208+
_ => buf.push_str(&self.format_args(format, args, hint)),
209+
},
210+
Arg::FormatSequence { args } => {
211+
for arg in args {
212+
buf.push_str(&self.format_args("{=?}", &[arg.clone()], hint))
213+
}
214+
}
215+
Arg::FormatSlice { elements } => {
216+
match hint {
217+
// Filter Ascii Hints, which contains u8 byte slices
218+
Some(DisplayHint::Ascii)
219+
if elements.iter().filter(|e| e.format == "{=u8}").count() != 0 =>
220+
{
221+
let vals = elements
222+
.iter()
223+
.map(|e| match e.args.as_slice() {
224+
[Arg::Uxx(v)] => {
225+
u8::try_from(*v).expect("the value must be in u8 range")
197226
}
198-
is_first = false;
199-
buf.write_str(&self.format_args(
200-
element.format,
201-
&element.args,
202-
hint,
203-
))?;
227+
_ => panic!("FormatSlice should only contain one argument"),
228+
})
229+
.collect::<Vec<u8>>();
230+
self.format_bytes(&vals, hint, buf)?
231+
}
232+
_ => {
233+
buf.write_str("[")?;
234+
let mut is_first = true;
235+
for element in elements {
236+
if !is_first {
237+
buf.write_str(", ")?;
204238
}
205-
buf.write_str("]")?;
239+
is_first = false;
240+
buf.write_str(&self.format_args(
241+
element.format,
242+
&element.args,
243+
hint,
244+
))?;
206245
}
246+
buf.write_str("]")?;
207247
}
208248
}
209-
Arg::Slice(x) => self.format_bytes(x, hint, &mut buf)?,
210-
Arg::Char(c) => write!(buf, "{c}")?,
211249
}
250+
Arg::Slice(x) => self.format_bytes(x, hint, buf)?,
251+
Arg::Char(c) => write!(buf, "{c}")?,
212252
}
213253
}
214254
}
215-
Ok(buf)
255+
256+
Ok(())
216257
}
217258

218259
fn format_u128(
@@ -531,6 +572,23 @@ impl fmt::Display for DisplayMessage<'_> {
531572
}
532573
}
533574

575+
pub struct DisplayFragments<'t> {
576+
frame: &'t Frame<'t>,
577+
iter: std::vec::IntoIter<Fragment<'t>>,
578+
}
579+
580+
impl Iterator for DisplayFragments<'_> {
581+
type Item = String;
582+
583+
fn next(&mut self) -> Option<Self::Item> {
584+
let mut buf = String::new();
585+
self.frame
586+
.format_fragment(self.iter.next()?, &mut buf, &self.frame.args, None)
587+
.ok()?;
588+
Some(buf)
589+
}
590+
}
591+
534592
/// Prints a `Frame` when formatted via `fmt::Display`, including all included metadata (level,
535593
/// timestamp, ...).
536594
pub struct DisplayFrame<'t> {

0 commit comments

Comments
 (0)