Control: tags -1 + patch
Hi Peter, On Tue, 2026-08-04 at 08:47 +0100, Peter Green wrote: > Package: rust-dtui > Version: 3.0.0-3 > Severity: serious > Tags: ftbfs > x-debbugs-cc: [email protected] > > rust-chumsky was recently updated to 0.13, leaving the build-depends > of rust-dtui unsatisfiable. > > I've looked at updating but it looks non-trivial, the upstream changelog > says that 0.10 was a rewrite and bumping the dependency results in a wall > of errors. > > I've also filed an upstream issue at > https://github.com/Troels51/dtui/issues/14 > > Maybe we have to introduce a semver-suffix package here. Please find attached a patch which allows dtui to use the new 0.13 API. It builds locally with cargo and the upstream repo; but I didn't have time to integrate into debcargo-config. Cheers! Chris
From 0e6a9f49fff42aa6ea43e9e5b726584c5e48bfd9 Mon Sep 17 00:00:00 2001 From: Christopher Obbard <[email protected]> Date: Tue, 4 Aug 2026 15:59:54 +0100 Subject: [PATCH] bump chumsky to 0.13 Closes: #1143559 Signed-off-by: Christopher Obbard <[email protected]> --- Cargo.toml | 2 +- src/bin/dtui/components/call_view.rs | 35 +-- src/bin/dtui/messages.rs | 1 - src/bin/dtui/parser.rs | 310 ++++++++++++--------------- 4 files changed, 159 insertions(+), 189 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d194eb2..25f4f02 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ tracing = "0.1.41" tracing-subscriber = { version = "0.3.20", features = ["env-filter", "json", "fmt"] } tracing-journald = "0.3.1" tui-textarea = "0.7" -chumsky = "0.9.3" +chumsky = "0.13" zbus = {version = "5.11.0", features = ["tokio", "serde_bytes"]} zbus_names = "4.2.0" zbus_xml = "5.0.2" diff --git a/src/bin/dtui/components/call_view.rs b/src/bin/dtui/components/call_view.rs index c833210..c4bcf30 100644 --- a/src/bin/dtui/components/call_view.rs +++ b/src/bin/dtui/components/call_view.rs @@ -24,10 +24,19 @@ use crate::{ pub struct MethodArgVisual { pub text_area: tui_textarea::TextArea<'static>, - pub parser: - Box<dyn Parser<char, zbus::zvariant::Value<'static>, Error = chumsky::error::Simple<char>>>, + // chumsky parsers borrow from the input they parse, so we keep the signature around and + // build a parser on demand instead of storing one. + pub signature: zbus::zvariant::Signature, pub is_input: bool, // Is this Arg an input or output } + +impl MethodArgVisual { + fn parse(&self) -> Result<zbus::zvariant::Value<'static>, Vec<chumsky::error::Rich<'_, char>>> { + get_parser(self.signature.clone()) + .parse(self.text_area.lines()[0].as_str()) + .into_result() + } +} // Encapsulates the information about the ongoing call struct OngoingCallInfo { invocation: Invocation, @@ -63,17 +72,16 @@ impl OngoingCallInfo { .title(format!("name: {} | {}", arg.name().unwrap(), inout)) .title_bottom(format!("type: {}", arg.ty().to_string())), ); - let parser = get_parser( + let signature = zbus::zvariant::Signature::from_str(arg.ty().to_string().as_str()) - .expect("The type description for the method we got was not good"), - ); + .expect("The type description for the method we got was not good"); let input = match arg.direction().unwrap_or(zbus_xml::ArgDirection::In) { zbus_xml::ArgDirection::In => true, zbus_xml::ArgDirection::Out => false, }; call_info.method_arg_vis.push(MethodArgVisual { text_area, - parser: Box::new(parser), + signature, is_input: input, }); } @@ -90,13 +98,12 @@ impl OngoingCallInfo { .title(format!("name: {} | {}", property.name(), "input")) .title_bottom(format!("type: {}", property.ty().to_string())), ); - let parser = Box::new(get_parser( + let signature = zbus::zvariant::Signature::from_str(property.ty().to_string().as_str()) - .expect("The type description for the method we got was not good"), - )); + .expect("The type description for the method we got was not good"); call_info.method_arg_vis.push(MethodArgVisual { text_area, - parser, + signature, is_input: true, }); @@ -161,9 +168,7 @@ impl CallView { let segments = single_line_layout.split(area); for (i, input) in ongoing.method_arg_vis.iter_mut().enumerate() { let emphasis = if i == ongoing.selected && active { - let method_arg: String = input.text_area.lines()[0].clone(); - let parsed = input.parser.parse(method_arg); - match parsed { + match input.parse() { Ok(_) => Style::default().fg(Color::Green), Err(_) => Style::default().fg(Color::Red), } @@ -230,11 +235,11 @@ impl Component for CallView { .method_arg_vis .iter() .filter(|input| input.is_input) - .map(|input| input.parser.parse(input.text_area.lines()[0].clone())); + .map(|input| input.parse()); if parses.clone().all( |result: Result< zbus::zvariant::Value<'static>, - Vec<chumsky::error::Simple<char>>, + Vec<chumsky::error::Rich<'_, char>>, >| Result::is_ok(&result), ) { let values: Vec<zbus::zvariant::OwnedValue> = parses diff --git a/src/bin/dtui/messages.rs b/src/bin/dtui/messages.rs index 684331c..85bf0d0 100644 --- a/src/bin/dtui/messages.rs +++ b/src/bin/dtui/messages.rs @@ -1,6 +1,5 @@ use std::collections::HashMap; -use chumsky::chain::Chain; use zbus::{ names::{OwnedBusName, OwnedInterfaceName, OwnedMemberName}, zvariant::{OwnedObjectPath, OwnedValue}, diff --git a/src/bin/dtui/parser.rs b/src/bin/dtui/parser.rs index 205974f..b098139 100644 --- a/src/bin/dtui/parser.rs +++ b/src/bin/dtui/parser.rs @@ -1,7 +1,13 @@ +use chumsky::input::InputRef; use chumsky::prelude::*; use std::{collections::HashMap, str::FromStr, u32}; use zbus::zvariant::{self, ObjectPath, Signature, StructureBuilder}; +/// The extra parser state used by every parser in this module. +type Extra<'src> = extra::Err<Rich<'src, char>>; +/// A boxed parser over a string input, producing a dbus value. +type ValueParser<'src> = Boxed<'src, 'src, &'src str, zvariant::Value<'static>, Extra<'src>>; + /// Create a parser from a Signature. /// The language that this parses is a human readable version of the dbus format. /// Arrays are delimited by [], with values seperated by "," @@ -14,12 +20,10 @@ use zbus::zvariant::{self, ObjectPath, Signature, StructureBuilder}; /// /// ``` /// let signature = Signature::from_str("as").unwrap(); -/// let result = get_parser(signature).parse("[\"first\", \"second\"]"); +/// let result = get_parser(signature).parse("[\"first\", \"second\"]").into_result(); /// assert_eq!(result, Ok(zvariant::Value::Array(vec!["first", "second"].into()))); /// ``` -pub fn get_parser( - signature: Signature, -) -> impl Parser<char, zvariant::Value<'static>, Error = Simple<char>> { +pub fn get_parser<'src>(signature: Signature) -> ValueParser<'src> { match signature { zvariant::Signature::Unit => todo!(), zvariant::Signature::U8 => parser_u8().boxed(), @@ -34,7 +38,7 @@ pub fn get_parser( zvariant::Signature::Str => parser_string().boxed(), zvariant::Signature::Signature => parser_signature().boxed(), zvariant::Signature::ObjectPath => parser_object_path().boxed(), - zvariant::Signature::Variant => parser_variant().boxed(), + zvariant::Signature::Variant => parser_variant(), zvariant::Signature::Fd => parser_fd().boxed(), zvariant::Signature::Array(child) => parser_array(child.signature().clone()).boxed(), zvariant::Signature::Dict { key, value } => { @@ -44,33 +48,45 @@ pub fn get_parser( } } -fn parser_variant<'a>() -> impl Parser<char, zvariant::Value<'static>, Error = Simple<char>> { - parser_signature() - .boxed() - .then_ignore(just("->")) - .then_with(|s| match s { - zvariant::Value::Signature(signature) => { - get_parser(signature).map(|variant| zvariant::Value::Value(Box::new(variant))) - } +/// The inner parser of a variant depends on the signature that precedes it, so it can only be +/// built once that signature has been parsed. `custom` gives us access to the input so we can +/// run a parser that is constructed on the fly. +fn parser_variant<'src>() -> ValueParser<'src> { + custom::<_, &'src str, _, Extra<'src>>(|input: &mut InputRef<'src, '_, _, _>| { + let signature = match input.parse(parser_signature())? { + zvariant::Value::Signature(signature) => signature, _ => unreachable!(), - }) + }; + input.parse(just("->"))?; + let value = input.parse(get_parser(signature))?; + Ok(zvariant::Value::Value(Box::new(value))) + }) + .boxed() } // -fn parser_struct<'a>( +fn parser_struct<'src>( structure: zvariant::signature::Fields, -) -> impl Parser<char, zvariant::Value<'a>, Error = Simple<char>> { +) -> impl Parser<'src, &'src str, zvariant::Value<'static>, Extra<'src>> + Clone { let mut element_parsers = structure .iter() .map(|signature: &zbus::zvariant::Signature| get_parser(signature.clone())); - let mut full_parser = just('(').map(|_| Vec::<zvariant::Value<'_>>::new()).boxed(); // The map is there to get types to match as the chain in the loop needs the parser to output a Vec - full_parser = full_parser.chain(element_parsers.next().unwrap()).boxed(); // The first doesnt get a ',' the rest do + // The first element doesn't get a ',' the rest do + let mut full_parser = element_parsers + .next() + .unwrap() + .map(|value| vec![value]) + .boxed(); for element_parser in element_parsers { full_parser = full_parser .then_ignore(just(",").padded()) - .chain(element_parser) + .then(element_parser) + .map(|(mut fields, field)| { + fields.push(field); + fields + }) .boxed(); } - full_parser = full_parser.then_ignore(just(')').padded()).boxed(); + let full_parser = full_parser.delimited_by(just('(').padded(), just(')').padded()); full_parser.map(|fields| { let mut builder = StructureBuilder::new(); @@ -80,10 +96,10 @@ fn parser_struct<'a>( zvariant::Value::Structure(builder.build().unwrap()) }) } -fn parser_dict<'a>( +fn parser_dict<'src>( key_type: Signature, value_type: Signature, -) -> impl Parser<char, zvariant::Value<'a>, Error = Simple<char>> { +) -> impl Parser<'src, &'src str, zvariant::Value<'static>, Extra<'src>> + Clone { let key_parser = get_parser(key_type.clone()); let value_parser = get_parser(value_type.clone()); let member_parser = key_parser @@ -91,12 +107,9 @@ fn parser_dict<'a>( .then(value_parser) .boxed(); member_parser - .clone() - .chain(just(',').padded().ignore_then(member_parser).repeated()) - .or_not() - .flatten() + .separated_by(just(',').padded()) + .collect::<HashMap<zvariant::Value<'static>, zvariant::Value<'static>>>() .delimited_by(just('{').padded(), just('}').padded()) - .collect::<HashMap<zvariant::Value<'_>, zvariant::Value<'_>>>() .map( move |m: HashMap<zvariant::Value<'_>, zvariant::Value<'_>>| { let mut dict = @@ -109,20 +122,13 @@ fn parser_dict<'a>( ) } -fn parser_array<'a>( +fn parser_array<'src>( signature: Signature, -) -> impl Parser<char, zvariant::Value<'a>, Error = Simple<char>> { - let element_parser = get_parser(signature.clone()).boxed(); +) -> impl Parser<'src, &'src str, zvariant::Value<'static>, Extra<'src>> + Clone { + let element_parser = get_parser(signature.clone()); element_parser - .clone() - .chain( - just(',') - .padded() - .ignore_then(element_parser.clone()) - .repeated(), - ) - .or_not() - .flatten() + .separated_by(just(',').padded()) + .collect::<Vec<zvariant::Value<'static>>>() .delimited_by(just('['), just(']')) .map(move |v: Vec<zvariant::Value<'_>>| { let mut array: zvariant::Array<'_> = zvariant::Array::new(&signature); @@ -136,201 +142,161 @@ fn parser_array<'a>( } // TODO: Can these be made generic, not sure how as they are generic over the Value type which is enums // TODO: Validation on sizes of numbers -fn parser_u8() -> impl Parser<char, zvariant::Value<'static>, Error = Simple<char>> { +fn parser_u8<'src>() -> impl Parser<'src, &'src str, zvariant::Value<'static>, Extra<'src>> + Clone { text::digits(10) + .to_slice() .labelled("u8") - .map(|s: String| zvariant::Value::U8(s.parse().unwrap())) + .map(|s: &str| zvariant::Value::U8(s.parse().unwrap())) .padded() } -fn parser_u16() -> impl Parser<char, zvariant::Value<'static>, Error = Simple<char>> { +fn parser_u16<'src>() -> impl Parser<'src, &'src str, zvariant::Value<'static>, Extra<'src>> + Clone +{ text::digits(10) + .to_slice() .labelled("u16") - .map(|s: String| zvariant::Value::U16(s.parse().unwrap())) + .map(|s: &str| zvariant::Value::U16(s.parse().unwrap())) .padded() } -fn parser_i16() -> impl Parser<char, zvariant::Value<'static>, Error = Simple<char>> { +fn parser_i16<'src>() -> impl Parser<'src, &'src str, zvariant::Value<'static>, Extra<'src>> + Clone +{ just('-') .or_not() - .chain::<char, _, _>(text::digits(10)) - .collect::<String>() - .map(|s: String| zvariant::Value::I16(s.parse().unwrap())) + .then(text::digits(10)) + .to_slice() + .map(|s: &str| zvariant::Value::I16(s.parse().unwrap())) .labelled("i16") .padded() } -fn parser_u32() -> impl Parser<char, zvariant::Value<'static>, Error = Simple<char>> { +fn parser_u32<'src>() -> impl Parser<'src, &'src str, zvariant::Value<'static>, Extra<'src>> + Clone +{ text::digits(10) + .to_slice() .labelled("u32") - .map(|s: String| zvariant::Value::U32(s.parse().unwrap())) + .map(|s: &str| zvariant::Value::U32(s.parse().unwrap())) .padded() } -fn parser_i32() -> impl Parser<char, zvariant::Value<'static>, Error = Simple<char>> { +fn parser_i32<'src>() -> impl Parser<'src, &'src str, zvariant::Value<'static>, Extra<'src>> + Clone +{ just('-') .or_not() - .chain::<char, _, _>(text::digits(10)) - .collect::<String>() - .map(|s: String| zvariant::Value::I32(s.parse().unwrap())) + .then(text::digits(10)) + .to_slice() + .map(|s: &str| zvariant::Value::I32(s.parse().unwrap())) .padded() } -fn parser_u64() -> impl Parser<char, zvariant::Value<'static>, Error = Simple<char>> { +fn parser_u64<'src>() -> impl Parser<'src, &'src str, zvariant::Value<'static>, Extra<'src>> + Clone +{ text::digits(10) + .to_slice() .labelled("u64") - .map(|s: String| zvariant::Value::U64(s.parse().unwrap())) + .map(|s: &str| zvariant::Value::U64(s.parse().unwrap())) .padded() } -fn parser_i64() -> impl Parser<char, zvariant::Value<'static>, Error = Simple<char>> { +fn parser_i64<'src>() -> impl Parser<'src, &'src str, zvariant::Value<'static>, Extra<'src>> + Clone +{ just('-') .or_not() - .chain::<char, _, _>(text::digits(10)) - .collect::<String>() + .then(text::digits(10)) + .to_slice() .labelled("i64") - .map(|s: String| zvariant::Value::I64(s.parse().unwrap())) + .map(|s: &str| zvariant::Value::I64(s.parse().unwrap())) .padded() } -fn parser_f64() -> impl Parser<char, zvariant::Value<'static>, Error = Simple<char>> { +fn parser_f64<'src>() -> impl Parser<'src, &'src str, zvariant::Value<'static>, Extra<'src>> + Clone +{ just('-') .or_not() - .chain::<char, _, _>(text::digits(10)) - .chain::<char, _, _>(just('.').chain(text::digits(10)).or_not().flatten()) - .collect::<String>() + .then(text::digits(10)) + .then(just('.').then(text::digits(10)).or_not()) + .to_slice() .labelled("f64") - .map(|s: String| zvariant::Value::F64(s.parse().unwrap())) + .map(|s: &str| zvariant::Value::F64(s.parse().unwrap())) .padded() } -fn parser_bool() -> impl Parser<char, zvariant::Value<'static>, Error = Simple<char>> { +fn parser_bool<'src>() -> impl Parser<'src, &'src str, zvariant::Value<'static>, Extra<'src>> + Clone +{ just("true") .map(|_| zvariant::Value::Bool(true)) .or(just("false").map(|_| zvariant::Value::Bool(false))) .labelled("bool") .padded() } -fn parser_string() -> impl Parser<char, zvariant::Value<'static>, Error = Simple<char>> { - let escape = just('\\').ignore_then( - just('\\') - .or(just('/')) - .or(just('"')) - .or(just('b').to('\x08')) - .or(just('f').to('\x0C')) - .or(just('n').to('\n')) - .or(just('r').to('\r')) - .or(just('t').to('\t')) - .or(just('u').ignore_then( - filter(|c: &char| c.is_ascii_hexdigit()) - .repeated() - .exactly(4) - .collect::<String>() - .validate(|digits, span, emit| { - char::from_u32(u32::from_str_radix(&digits, 16).unwrap()).unwrap_or_else( - || { - emit(Simple::custom(span, "invalid unicode character")); - '\u{FFFD}' // unicode replacement character - }, - ) - }), - )), - ); - let string = just('"') - .ignore_then(filter(|c| *c != '\\' && *c != '"').or(escape).repeated()) + +/// The quoted string literal that is shared by the string, signature and object path parsers. +fn quoted_string<'src>() -> impl Parser<'src, &'src str, String, Extra<'src>> + Clone { + let escape = just('\\').ignore_then(choice(( + just('\\'), + just('/'), + just('"'), + just('b').to('\x08'), + just('f').to('\x0C'), + just('n').to('\n'), + just('r').to('\r'), + just('t').to('\t'), + just('u').ignore_then( + any() + .filter(|c: &char| c.is_ascii_hexdigit()) + .repeated() + .exactly(4) + .to_slice() + .validate(|digits: &str, extra, emitter| { + char::from_u32(u32::from_str_radix(digits, 16).unwrap()).unwrap_or_else(|| { + emitter.emit(Rich::custom(extra.span(), "invalid unicode character")); + '\u{FFFD}' // unicode replacement character + }) + }), + ), + ))); + just('"') + .ignore_then(none_of("\\\"").or(escape).repeated().collect::<String>()) .then_ignore(just('"')) - .collect::<String>() +} + +fn parser_string<'src>() +-> impl Parser<'src, &'src str, zvariant::Value<'static>, Extra<'src>> + Clone { + quoted_string() .map(|s| zvariant::Value::Str(s.into())) - .labelled("string"); - string - .recover_with(skip_then_retry_until(['}', ']'])) + .labelled("string") + .recover_with(skip_then_retry_until(any().ignored(), one_of("}]").ignored())) .padded() } -fn parser_signature() -> impl Parser<char, zvariant::Value<'static>, Error = Simple<char>> { - let escape = just('\\').ignore_then( - just('\\') - .or(just('/')) - .or(just('"')) - .or(just('b').to('\x08')) - .or(just('f').to('\x0C')) - .or(just('n').to('\n')) - .or(just('r').to('\r')) - .or(just('t').to('\t')) - .or(just('u').ignore_then( - filter(|c: &char| c.is_ascii_hexdigit()) - .repeated() - .exactly(4) - .collect::<String>() - .validate(|digits, span, emit| { - char::from_u32(u32::from_str_radix(&digits, 16).unwrap()).unwrap_or_else( - || { - emit(Simple::custom(span, "invalid unicode character")); - '\u{FFFD}' // unicode replacement character - }, - ) - }), - )), - ); - let string = just('"') - .ignore_then(filter(|c| *c != '\\' && *c != '"').or(escape).repeated()) - .then_ignore(just('"')) - .collect::<String>() - .try_map(|digits, span| { +fn parser_signature<'src>() +-> impl Parser<'src, &'src str, zvariant::Value<'static>, Extra<'src>> + Clone { + quoted_string() + .try_map(|digits: String, span| { if let Ok(signature) = Signature::from_str(digits.as_str()) { Ok(zvariant::Value::Signature(signature)) } else { - Err(Simple::custom( + Err(Rich::custom( span, "Could not parse signature from string value", )) } }) - .labelled("signature"); - - string - .recover_with(skip_then_retry_until(['}', ']'])) + .labelled("signature") + .recover_with(skip_then_retry_until(any().ignored(), one_of("}]").ignored())) .padded() } -fn parser_object_path() -> impl Parser<char, zvariant::Value<'static>, Error = Simple<char>> { - let escape = just('\\').ignore_then( - just('\\') - .or(just('/')) - .or(just('"')) - .or(just('b').to('\x08')) - .or(just('f').to('\x0C')) - .or(just('n').to('\n')) - .or(just('r').to('\r')) - .or(just('t').to('\t')) - .or(just('u').ignore_then( - filter(|c: &char| c.is_ascii_hexdigit()) - .repeated() - .exactly(4) - .collect::<String>() - .validate(|digits, span, emit| { - char::from_u32(u32::from_str_radix(&digits, 16).unwrap()).unwrap_or_else( - || { - emit(Simple::custom(span, "invalid unicode character")); - '\u{FFFD}' // unicode replacement character - }, - ) - }), - )), - ); - let string = just('"') - .ignore_then(filter(|c| *c != '\\' && *c != '"').or(escape).repeated()) - .then_ignore(just('"')) - .collect::<String>() - .try_map(|digits, span| { +fn parser_object_path<'src>() +-> impl Parser<'src, &'src str, zvariant::Value<'static>, Extra<'src>> + Clone { + quoted_string() + .try_map(|digits: String, span| { if let Ok(path) = ObjectPath::try_from(digits) { Ok(zvariant::Value::ObjectPath(path)) } else { - Err(Simple::custom( + Err(Rich::custom( span, "Could not parse object path from string value", )) } }) - .labelled("object_path"); - - string - .recover_with(skip_then_retry_until(['}', ']'])) + .labelled("object_path") + .recover_with(skip_then_retry_until(any().ignored(), one_of("}]").ignored())) .padded() } -fn parser_fd() -> impl Parser<char, zvariant::Value<'static>, Error = Simple<char>> { - empty().try_map(|(), span| Err(Simple::custom(span, "Cannot parse file descriptors"))) +fn parser_fd<'src>() -> impl Parser<'src, &'src str, zvariant::Value<'static>, Extra<'src>> + Clone { + empty().try_map(|(), span| Err(Rich::custom(span, "Cannot parse file descriptors"))) } #[cfg(test)] @@ -339,7 +305,7 @@ fn test_generic_signature(src: &'static str, signature: &'static str, value: zva let signature = Signature::from_str(signature).unwrap(); println!("{}", signature); - let result = get_parser(signature).parse(src.trim()); + let result = get_parser(signature).parse(src.trim()).into_result(); dbg!(&result); dbg!(&value); assert_eq!(result, Ok(value)); @@ -386,7 +352,7 @@ fn test_signature() { ); use std::str::FromStr; let signature = Signature::from_str("g").unwrap(); - let result = get_parser(signature).parse("k"); // k is not a valid signature + let result = get_parser(signature).parse("k").into_result(); // k is not a valid signature assert!(result.is_err()); } @@ -410,10 +376,10 @@ fn test_object_path() { use std::str::FromStr; let signature = Signature::from_str("o").unwrap(); - let result = get_parser(signature).parse("k"); // k is not a valid object path + let result = get_parser(signature).parse("k").into_result(); // k is not a valid object path assert!(result.is_err()); let signature = Signature::from_str("o").unwrap(); - let result = get_parser(signature).parse("//"); // // is not a valid object path + let result = get_parser(signature).parse("//").into_result(); // // is not a valid object path assert!(result.is_err()); } -- 2.55.0

