Skip to content

Commit ec65bde

Browse files
committed
decoder: add Frame::fragments() and Frame::display_fragments()
1 parent 6128141 commit ec65bde

File tree

3 files changed

+222
-95
lines changed

3 files changed

+222
-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
[#968]: https://github.com/knurling-rs/defmt/pull/968
951953
[#965]: https://github.com/knurling-rs/defmt/pull/965
952954
[#960]: https://github.com/knurling-rs/defmt/pull/960

decoder/src/frame.rs

Lines changed: 159 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,52 @@ 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+
/// ```ignore
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+
/// ```ignore
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+
/// Returns the fragments of the message contained in this log frame.
131+
///
132+
/// Each fragment represents a part of the log message. See [`Fragment`] for more details.
133+
///
134+
/// This iterator yields the same fragments as [`Self::display_fragments`], so you can zip them
135+
/// together to get both representations.
136+
pub fn fragments(&'t self) -> Vec<Fragment<'t>> {
137+
defmt_parser::parse(self.format, ParserMode::ForwardsCompatible).unwrap()
138+
}
139+
94140
pub fn level(&self) -> Option<Level> {
95141
self.level
96142
}
@@ -100,119 +146,120 @@ impl<'t> Frame<'t> {
100146
}
101147

102148
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`
149+
let params = defmt_parser::parse(format, ParserMode::ForwardsCompatible).unwrap();
150+
let mut buf = String::new();
151+
for param in params {
152+
self.format_fragment(param, &mut buf, args, parent_hint)
153+
.unwrap(); // cannot fail, we only write to a `String`
154+
}
155+
buf
104156
}
105157

106-
fn format_args_real(
158+
fn format_fragment(
107159
&self,
108-
format: &str,
160+
param: Fragment<'_>,
161+
buf: &mut String,
109162
args: &[Arg],
110163
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-
}
164+
) -> Result<(), fmt::Error> {
165+
match param {
166+
Fragment::Literal(lit) => {
167+
buf.push_str(&lit);
168+
}
169+
Fragment::Parameter(param) => {
170+
let hint = param.hint.as_ref().or(parent_hint);
171+
172+
match &args[param.index] {
173+
Arg::Bool(x) => write!(buf, "{x}")?,
174+
Arg::F32(x) => write!(buf, "{}", ryu::Buffer::new().format(*x))?,
175+
Arg::F64(x) => write!(buf, "{}", ryu::Buffer::new().format(*x))?,
176+
Arg::Uxx(x) => {
177+
match param.ty {
178+
Type::BitField(range) => {
179+
let left_zeroes = mem::size_of::<u128>() * 8 - range.end as usize;
180+
let right_zeroes = left_zeroes + range.start as usize;
181+
// isolate the desired bitfields
182+
let bitfields = (*x << left_zeroes) >> right_zeroes;
183+
184+
if let Some(DisplayHint::Ascii) = hint {
185+
let bstr = bitfields
186+
.to_be_bytes()
187+
.iter()
188+
.skip(right_zeroes / 8)
189+
.copied()
190+
.collect::<Vec<u8>>();
191+
self.format_bytes(&bstr, hint, buf)?
192+
} else {
193+
self.format_u128(bitfields, hint, buf)?;
146194
}
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-
},
156195
}
196+
_ => match hint {
197+
Some(DisplayHint::ISO8601(precision)) => {
198+
self.format_iso8601(*x as u64, precision, buf)?
199+
}
200+
Some(DisplayHint::Debug) => {
201+
self.format_u128(*x, parent_hint, buf)?
202+
}
203+
_ => self.format_u128(*x, hint, buf)?,
204+
},
157205
}
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-
}
206+
}
207+
Arg::Ixx(x) => self.format_i128(*x, param.ty, hint, buf)?,
208+
Arg::Str(x) | Arg::Preformatted(x) => self.format_str(x, hint, buf)?,
209+
Arg::IStr(x) => self.format_str(x, hint, buf)?,
210+
Arg::Format { format, args } => match parent_hint {
211+
Some(DisplayHint::Ascii) => {
212+
buf.push_str(&self.format_args(format, args, parent_hint));
171213
}
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(", ")?;
214+
_ => buf.push_str(&self.format_args(format, args, hint)),
215+
},
216+
Arg::FormatSequence { args } => {
217+
for arg in args {
218+
buf.push_str(&self.format_args("{=?}", &[arg.clone()], hint))
219+
}
220+
}
221+
Arg::FormatSlice { elements } => {
222+
match hint {
223+
// Filter Ascii Hints, which contains u8 byte slices
224+
Some(DisplayHint::Ascii)
225+
if elements.iter().filter(|e| e.format == "{=u8}").count() != 0 =>
226+
{
227+
let vals = elements
228+
.iter()
229+
.map(|e| match e.args.as_slice() {
230+
[Arg::Uxx(v)] => {
231+
u8::try_from(*v).expect("the value must be in u8 range")
197232
}
198-
is_first = false;
199-
buf.write_str(&self.format_args(
200-
element.format,
201-
&element.args,
202-
hint,
203-
))?;
233+
_ => panic!("FormatSlice should only contain one argument"),
234+
})
235+
.collect::<Vec<u8>>();
236+
self.format_bytes(&vals, hint, buf)?
237+
}
238+
_ => {
239+
buf.write_str("[")?;
240+
let mut is_first = true;
241+
for element in elements {
242+
if !is_first {
243+
buf.write_str(", ")?;
204244
}
205-
buf.write_str("]")?;
245+
is_first = false;
246+
buf.write_str(&self.format_args(
247+
element.format,
248+
&element.args,
249+
hint,
250+
))?;
206251
}
252+
buf.write_str("]")?;
207253
}
208254
}
209-
Arg::Slice(x) => self.format_bytes(x, hint, &mut buf)?,
210-
Arg::Char(c) => write!(buf, "{c}")?,
211255
}
256+
Arg::Slice(x) => self.format_bytes(x, hint, buf)?,
257+
Arg::Char(c) => write!(buf, "{c}")?,
212258
}
213259
}
214260
}
215-
Ok(buf)
261+
262+
Ok(())
216263
}
217264

218265
fn format_u128(
@@ -531,6 +578,23 @@ impl fmt::Display for DisplayMessage<'_> {
531578
}
532579
}
533580

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

0 commit comments

Comments
 (0)