("4: [%1:, ^1:]1", "4: First, 1.00, Second, 2"),
("5: [%1 = %2:, ^1 = ^2:]1", "5: First = 1.00, Second = 2"),
("6: [%1:, ^1:]1", "6: First, 1.00, Second, 2"),
+ ("7: [%1:, ^1: and $1]1", "7: First, 1.00, Second and 2"),
+ ("8: [%1:, ^1: and $1]3", "8: One and two"),
+ ("9: [%1:, ^1: and $1]4", "9: Just one"),
] {
let value = Value::new(ValueInner::Template(TemplateValue {
args: vec![
Value::new_user_text("Fourth"),
Value::new_integer(Some(4.0)),
],
+ vec![Value::new_user_text("One"), Value::new_user_text("two")],
+ vec![Value::new_user_text("Just one")],
],
localized: String::from(template),
id: None,
display: &DisplayValue<'a>,
f: &mut std::fmt::Formatter<'_>,
) -> std::fmt::Result {
- fn extract_inner_template(input: &str) -> (&str, &str) {
- let mut prev = None;
+ dbg!(self);
+
+ #[derive(Copy, Clone, Debug)]
+ struct InnerTemplate<'b> {
+ template: &'b str,
+ escape: char,
+ }
+
+ impl<'b> InnerTemplate<'b> {
+ fn new(template: &'b str, escape: char) -> Self {
+ Self { template, escape }
+ }
+
+ fn extract(input: &'b str, escape: char, end: char) -> (Self, &'b str) {
+ let mut prev = None;
+ for (index, c) in input.char_indices() {
+ if c == end && prev != Some('\\') {
+ return (Self::new(&input[..index], escape), &input[index + 1..]);
+ }
+ prev = Some(c);
+ }
+ (Self::new(input, escape), "")
+ }
+
+ fn expand(
+ &self,
+ options: &ValueOptions,
+ f: &mut std::fmt::Formatter<'_>,
+ args: &mut std::slice::Iter<Value>,
+ ) -> Result<usize, std::fmt::Error> {
+ let mut iter = self.template.chars();
+
+ // Always consume at least 1 argument to avoid infinite loop.
+ let mut args_consumed = 1;
+
+ while let Some(c) = iter.next() {
+ match c {
+ '\\' => {
+ let c = iter.next().unwrap_or('\\') as char;
+ let c = if c == 'n' { '\n' } else { c };
+ write!(f, "{c}")?;
+ }
+ c if c == self.escape => {
+ let (index, rest) = consume_int(iter.as_str());
+ iter = rest.chars();
+ if let Some(index) = index.checked_sub(1)
+ && let Some(arg) = args.as_slice().get(index)
+ {
+ args_consumed = args_consumed.max(index + 1);
+ write!(f, "{}", arg.display(options))?;
+ }
+ }
+ c => write!(f, "{c}")?,
+ }
+ }
+ for _ in 0..args_consumed {
+ args.next();
+ }
+ Ok(args_consumed)
+ }
+ }
+
+ fn consume_int(input: &str) -> (usize, &str) {
+ let mut n = 0;
for (index, c) in input.char_indices() {
- if c == ':' && prev != Some('\\') {
- return (&input[..index], &input[index + 1..]);
+ match c.to_digit(10) {
+ Some(digit) => n = n * 10 + digit as usize,
+ None => return (n, &input[index..]),
}
- prev = Some(c);
}
- (input, "")
+ (n, "")
}
// Arguments are formatted without leading zeros for `PCT` and `DOLLAR`.
f.write_char(c)?;
}
'^' => {
- let (index, rest) = Self::consume_int(iter.as_str());
+ let (index, rest) = consume_int(iter.as_str());
if let Some(index) = index.checked_sub(1)
&& let Some(arg) = self.args.get(index)
&& let Some(arg) = arg.first()
iter = rest.chars();
}
'[' => {
- let (a, rest) = extract_inner_template(iter.as_str());
- let (b, rest) = extract_inner_template(rest);
- let rest = rest.strip_prefix("]").unwrap_or(rest);
- let (index, rest) = Self::consume_int(rest);
+ let (a, rest) = InnerTemplate::extract(iter.as_str(), '%', ':');
+ let (b, rest) = InnerTemplate::extract(rest, '^', ':');
+ let (c, rest) = InnerTemplate::extract(rest, '$', ']');
+ let (index, rest) = consume_int(rest);
iter = rest.chars();
+ let (first, mid, last) = if a.template.is_empty() {
+ (b, b, b)
+ } else if c.template.is_empty() {
+ (a, b, b)
+ } else {
+ (a, b, c)
+ };
if let Some(index) = index.checked_sub(1)
&& let Some(args) = self.args.get(index)
{
- let mut args = args.as_slice();
- let (mut template, mut escape) =
- if !a.is_empty() { (a, '%') } else { (b, '^') };
- while !args.is_empty()
- && let n_consumed =
- self.inner_template(&options, f, template, escape, args)?
- && n_consumed > 0
- {
- args = &args[n_consumed..];
- template = b;
- escape = '^';
+ let mut args = args.iter();
+ let n = first.expand(&options, f, &mut args)?;
+ while args.len() > n {
+ mid.expand(&options, f, &mut args)?;
+ }
+ if args.len() > 0 {
+ last.expand(&options, f, &mut args)?;
}
}
}
}
Ok(())
}
-
- fn inner_template<'a>(
- &self,
- options: &ValueOptions,
- f: &mut std::fmt::Formatter<'_>,
- template: &str,
- escape: char,
- args: &[Value],
- ) -> Result<usize, std::fmt::Error> {
- let mut iter = template.chars();
- let mut args_consumed = 0;
- while let Some(c) = iter.next() {
- match c {
- '\\' => {
- let c = iter.next().unwrap_or('\\') as char;
- let c = if c == 'n' { '\n' } else { c };
- write!(f, "{c}")?;
- }
- c if c == escape => {
- let (index, rest) = Self::consume_int(iter.as_str());
- iter = rest.chars();
- if let Some(index) = index.checked_sub(1)
- && let Some(arg) = args.get(index)
- {
- args_consumed = args_consumed.max(index + 1);
- write!(f, "{}", arg.display(options))?;
- }
- }
- c => write!(f, "{c}")?,
- }
- }
- Ok(args_consumed)
- }
-
- fn consume_int(input: &str) -> (usize, &str) {
- let mut n = 0;
- for (index, c) in input.char_indices() {
- match c.to_digit(10) {
- Some(digit) => n = n * 10 + digit as usize,
- None => return (n, &input[index..]),
- }
- }
- (n, "")
- }
}
/// Possible content for a [Value].