aimx/values/format.rs
1//! Format values for controlling output formatting in AIMX expressions.
2//!
3//! This module provides the `Format` struct and related functionality for
4//! representing formatting instructions in the AIMX expression language.
5//! Format values are used to control how values are displayed or serialized,
6//! providing template-like functionality for custom output generation.
7//!
8//! # Format Structure
9//!
10//! A `Format` value consists of three components:
11//! - `instruction`: The main instruction text that describes the formatting operation
12//! - `format`: A format specification enclosed in angle brackets (`<format>`)
13//! - `array`: A list of example values that demonstrate the format
14//!
15//! The format follows the pattern: `"Instruction" <format> "Example1", "Example2", ...`
16//!
17//! # Usage
18//!
19//! Format values are typically used in workflow rules to define how inference
20//! results should be formatted or to provide templates for output generation.
21//! They can be evaluated and written using the standard expression evaluation
22//! infrastructure, supporting escaping and quoting modes for different contexts.
23//!
24//! # Examples
25//!
26//! Creating format values in Rust code:
27//!
28//! ```
29//! use aimx::values::Format;
30//! use aimx::Value;
31//!
32//! let format_value = Format::new(
33//! "Display as currency".to_string(),
34//! "$0.00".to_string(),
35//! vec!["$42.00".to_string(), "$123.45".to_string()]
36//! );
37//!
38//! // Check that we have a format value
39//! assert!(format_value.is_format());
40//! ```
41//!
42//! See also: [`crate::typedef::Typedef::Format`], [`parse_format`], [`crate::writer::Writer::print_format`]
43
44use nom::{
45 IResult, Parser,
46 combinator::map,
47 character::complete::{char, multispace0},
48 multi::separated_list0,
49 sequence::preceded,
50};
51use crate::{
52 ContextLike,
53 ExpressionLike,
54 literals::{parse_text, parse_tag},
55 Value,
56 Writer,
57};
58use std::fmt;
59use anyhow::Result;
60
61/// Represents a formatting instruction value in the AIMX expression language.
62///
63/// Format values provide a way to specify how values should be formatted
64/// when displayed or serialized. They follow the pattern:
65/// `"Instruction" <format> "Example1", "Example2", ...`
66///
67/// # Structure
68///
69/// A `Format` contains three components:
70/// - `instruction`: Human-readable description of what the format does
71/// - `format`: The actual format specification template
72/// - `array`: Examples demonstrating the format with different values
73///
74/// # Examples
75///
76/// ```rust
77/// use aimx::values::Format;
78/// use aimx::Value;
79///
80/// let currency_format = Format::new(
81/// "Display as currency".to_string(),
82/// "$0.00".to_string(),
83/// vec!["$42.00".to_string(), "$123.45".to_string()]
84/// );
85///
86/// // Extract the Format struct from the Value enum
87/// if let Value::Format(ref format) = currency_format {
88/// assert_eq!(format.instruction(), "Display as currency");
89/// assert_eq!(format.format(), "$0.00");
90/// assert_eq!(format.array().len(), 2);
91/// } else {
92/// panic!("Expected Value::Format");
93/// }
94/// ```
95///
96/// See also: [`parse_format`], [`crate::typedef::Typedef::Format`], [`crate::value::Value::Format`]
97#[derive(Debug, Clone, PartialEq)]
98pub struct Format {
99 instruction: String,
100 format: String,
101 array: Vec<String>
102}
103
104impl Format {
105 /// Creates a new Format value wrapped in a Value enum.
106 ///
107 /// This constructor creates a new `Format` instance and returns it wrapped
108 /// as a `Value::Format` variant. This is the primary way to create format
109 /// values for use in the expression evaluation system.
110 ///
111 /// # Arguments
112 ///
113 /// * `instruction` - Human-readable description of the formatting operation
114 /// * `format` - The format specification template
115 /// * `array` - Examples demonstrating the format with different values
116 ///
117 /// # Returns
118 ///
119 /// * `Value` - A `Value::Format` variant containing the new format
120 ///
121 /// # Examples
122 ///
123 /// ```rust
124 /// use aimx::{Value, values::Format};
125 ///
126 /// let format_value = Format::new(
127 /// "Display as percentage".to_string(),
128 /// "0.0%".to_string(),
129 /// vec!["42.5%".to_string(), "99.9%".to_string()]
130 /// );
131 ///
132 /// assert!(format_value.is_format());
133 /// ```
134 pub fn new(instruction: String, format: String, array: Vec<String>) -> Value {
135 Value::Format(Format{instruction, format, array})
136 }
137
138 /// Returns a reference to the instruction text.
139 ///
140 /// The instruction provides a human-readable description of what the
141 /// formatting operation does, such as "Display as currency" or "Format as date".
142 ///
143 /// # Returns
144 ///
145 /// * `&str` - A reference to the instruction string
146 ///
147 /// # Examples
148 ///
149 /// ```rust
150 /// use aimx::values::Format;
151 /// use aimx::Value;
152 ///
153 /// let format_value = Format::new(
154 /// "Format as currency".to_string(),
155 /// "$0.00".to_string(),
156 /// vec![]
157 /// );
158 /// assert!(format_value.is_format());
159 ///
160 /// // Extract the Format struct from the Value enum
161 /// if let Value::Format(ref format) = format_value {
162 /// assert_eq!(format.instruction(), "Format as currency");
163 /// } else {
164 /// panic!("Expected Value::Format");
165 /// }
166 /// ```
167 pub fn instruction(&self) -> &str {
168 &self.instruction
169 }
170
171 /// Returns a reference to the format specification.
172 ///
173 /// The format specification is the actual template that defines how values
174 /// should be formatted, such as "$0.00" for currency or "MM/DD/YYYY" for dates.
175 ///
176 /// # Returns
177 ///
178 /// * `&str` - A reference to the format specification string
179 ///
180 /// # Examples
181 ///
182 /// ```rust
183 /// use aimx::values::Format;
184 /// use aimx::Value;
185 ///
186 /// let format_value = Format::new(
187 /// "Display as currency".to_string(),
188 /// "$0.00".to_string(),
189 /// vec![]
190 /// );
191 /// assert!(format_value.is_format());
192 ///
193 /// // Extract the Format struct from the Value enum
194 /// if let Value::Format(ref format) = format_value {
195 /// assert_eq!(format.format(), "$0.00");
196 /// } else {
197 /// panic!("Expected Value::Format");
198 /// }
199 /// ```
200 pub fn format(&self) -> &str {
201 &self.format
202 }
203
204 /// Returns a reference to the array of example values.
205 ///
206 /// The examples demonstrate how the format works with different values,
207 /// providing concrete examples of the expected output.
208 ///
209 /// # Returns
210 ///
211 /// * `&Vec<String>` - A reference to the vector of example strings
212 ///
213 /// # Examples
214 ///
215 /// ```rust
216 /// use aimx::values::Format;
217 /// use aimx::Value;
218 ///
219 /// let format_value = Format::new(
220 /// "Display numbers".to_string(),
221 /// "0.00".to_string(),
222 /// vec!["42.00".to_string(), "123.45".to_string()]
223 /// );
224 /// assert!(format_value.is_format());
225 ///
226 /// // Extract the Format struct from the Value enum
227 /// if let Value::Format(ref format) = format_value {
228 /// let examples = format.array();
229 /// assert_eq!(examples.len(), 2);
230 /// assert_eq!(examples[0], "42.00");
231 /// assert_eq!(examples[1], "123.45");
232 /// } else {
233 /// panic!("Expected Value::Format");
234 /// }
235 /// ```
236 pub fn array(&self) -> &Vec<String> {
237 &self.array
238 }
239}
240
241/// Parses a format value from input text.
242///
243/// This function parses format values according to the AIMX grammar syntax:
244/// `"Instruction" <format> "Example1", "Example2", ...`
245///
246/// The parser expects:
247/// 1. Optional whitespace
248/// 2. A quoted instruction text
249/// 3. Optional whitespace
250/// 4. A format specification enclosed in angle brackets
251/// 5. Optional whitespace
252/// 6. A comma-separated list of quoted example texts
253///
254/// # Arguments
255///
256/// * `input` - The input string to parse
257///
258/// # Returns
259///
260/// * `IResult<&str, Value>` - A nom result with remaining input and parsed format value
261///
262/// # Examples
263///
264/// ```rust
265/// use aimx::values::parse_format;
266///
267/// let result = parse_format(r#""Display as currency" <$0.00> "$42.00", "$123.45""#);
268/// assert!(result.is_ok());
269///
270/// let (remaining, format_value) = result.unwrap();
271/// assert!(format_value.is_format());
272/// assert_eq!(remaining, "");
273/// ```
274///
275/// See also: [`crate::values::format::parse_string_array`], [`crate::literals::parse_text`], [`crate::literals::parse_tag`]
276pub fn parse_format(input: &str) -> IResult<&str, Value> {
277 map(
278 (
279 multispace0,
280 parse_text,
281 multispace0,
282 parse_tag,
283 multispace0,
284 parse_string_array,
285 ),
286 |(_, instruction, _, format, _, array)| Value::Format(Format{instruction, format, array})
287 )
288 .parse(input)
289}
290
291/// Parses a comma-separated list of string values.
292///
293/// This helper function parses a list of quoted text values separated by commas.
294/// It handles optional whitespace around commas and uses the standard text parsing
295/// logic for each individual value.
296///
297/// # Arguments
298///
299/// * `input` - The input string to parse
300///
301/// # Returns
302///
303/// * `IResult<&str, Vec<String>>` - A nom result with remaining input and parsed string vector
304///
305/// # Examples
306///
307/// ```rust
308/// use aimx::values::format::parse_string_array;
309///
310/// let result = parse_string_array(r#""first", "second", "third""#);
311/// assert!(result.is_ok());
312///
313/// let (remaining, strings) = result.unwrap();
314/// assert_eq!(strings.len(), 3);
315/// assert_eq!(strings[0], "first");
316/// assert_eq!(strings[1], "second");
317/// assert_eq!(strings[2], "third");
318/// assert_eq!(remaining, "");
319/// ```
320pub fn parse_string_array(input: &str) -> IResult<&str, Vec<String>> {
321 separated_list0(
322 preceded(multispace0, char(',')),
323 preceded(multispace0, parse_text),
324 )
325 .parse(input)
326}
327
328impl ExpressionLike for Format {
329 /// Evaluates the format value, returning itself unchanged.
330 ///
331 /// Format values are constant and evaluate to themselves. This method
332 /// implements the required evaluation behavior for the ExpressionLike trait.
333 ///
334 /// # Arguments
335 ///
336 /// * `_context` - The evaluation context (unused for format values)
337 ///
338 /// # Returns
339 ///
340 /// * `Result<Value>` - The format value itself wrapped in a result
341 ///
342 /// # Examples
343 ///
344 /// ```rust
345 /// use aimx::{ExpressionLike, Context, values::Format};
346 ///
347 /// let format = Format::new(
348 /// "Display as percentage".to_string(),
349 /// "0.0%".to_string(),
350 /// vec!["42.5%".to_string()]
351 /// );
352 ///
353 /// let mut context = Context::new();
354 /// let result = format.evaluate(&mut context).unwrap();
355 ///
356 /// assert!(result.is_format());
357 /// ```
358 fn evaluate(&self, _context: &mut dyn ContextLike) -> Result<Value> {
359 Ok(Value::Format(self.clone()))
360 }
361
362 /// Writes the format value to a writer using the appropriate formatting mode.
363 ///
364 /// This method delegates to the writer's `print_format` method, which
365 /// handles the different output modes (raw, escaped, quoted).
366 ///
367 /// # Arguments
368 ///
369 /// * `writer` - The writer to write the formatted output to
370 ///
371 /// # Examples
372 ///
373 /// ```rust
374 /// use aimx::{ExpressionLike, Writer, PrintMode, Prefix, values::Format, Value};
375 ///
376 /// let format_value = Format::new(
377 /// "Display as currency".to_string(),
378 /// "$0.00".to_string(),
379 /// vec!["$42.00".to_string()]
380 /// );
381 /// assert!(format_value.is_format());
382 ///
383 /// // Extract the Format struct from the Value enum
384 /// if let Value::Format(ref format) = format_value {
385 /// let mut writer = Writer::new(PrintMode::None, Prefix::None);
386 /// format.write(&mut writer);
387 /// let output = writer.finish();
388 ///
389 /// assert!(output.contains("Display as currency"));
390 /// assert!(output.contains("$0.00"));
391 /// assert!(output.contains("$42.00"));
392 /// } else {
393 /// panic!("Expected Value::Format");
394 /// }
395 /// ```
396 fn write(&self, writer: &mut Writer) {
397 writer.print_format(&self.instruction, &self.format, &self.array);
398 }
399
400 /// Converts the format value to a sanitized string representation.
401 ///
402 /// This method produces a string with special characters escaped,
403 /// making it safe for contexts like JSON serialization or logging.
404 ///
405 /// # Returns
406 ///
407 /// * `String` - A sanitized string representation of the format
408 ///
409 /// # Examples
410 ///
411 /// ```rust
412 /// use aimx::{ExpressionLike, values::Format};
413 ///
414 /// let format = Format::new(
415 /// "Format with quotes\"".to_string(),
416 /// "<template>".to_string(),
417 /// vec!["example\"quoted".to_string()]
418 /// );
419 ///
420 /// let sanitized = format.to_sanitized();
421 /// // Special characters like quotes will be escaped
422 /// assert!(sanitized.contains("\\\""));
423 /// ```
424 fn to_sanitized(&self) -> String {
425 let mut writer = Writer::sanitizer();
426 writer.print_format(&self.instruction, &self.format, &self.array);
427 writer.finish()
428 }
429
430 /// Converts the format value to a formula string representation.
431 ///
432 /// This method produces a string with proper quoting and escaping
433 /// for use in formulas, making it round-trippable through parsing.
434 ///
435 /// # Returns
436 ///
437 /// * `String` - A formula string representation of the format
438 ///
439 /// # Examples
440 ///
441 /// ```rust
442 /// use aimx::{ExpressionLike, values::Format};
443 ///
444 /// let format = Format::new(
445 /// "Display as percentage".to_string(),
446 /// "0.0%".to_string(),
447 /// vec!["42.5%".to_string()]
448 /// );
449 ///
450 /// let formula = format.to_formula();
451 /// // Values will be properly quoted for formula context
452 /// assert!(formula.contains('"'));
453 /// ```
454 fn to_formula(&self) -> String {
455 let mut writer = Writer::formulizer();
456 writer.print_format(&self.instruction, &self.format, &self.array);
457 writer.finish()
458 }
459}
460
461impl fmt::Display for Format {
462 /// Formats the format value for human-readable display.
463 ///
464 /// This implementation uses the stringizer writer mode to produce
465 /// a clean, human-readable representation of the format value.
466 ///
467 /// # Arguments
468 ///
469 /// * `f` - The formatter to write to
470 ///
471 /// # Returns
472 ///
473 /// * `fmt::Result` - The result of the formatting operation
474 ///
475 /// # Examples
476 ///
477 /// ```rust
478 /// use aimx::values::Format;
479 ///
480 /// let format = Format::new(
481 /// "Display as currency".to_string(),
482 /// "$0.00".to_string(),
483 /// vec!["$42.00".to_string()]
484 /// );
485 ///
486 /// let display_string = format.to_string();
487 /// assert!(display_string.contains("Display as currency"));
488 /// assert!(display_string.contains("$0.00"));
489 /// ```
490 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
491 let mut writer = Writer::stringizer();
492 writer.print_format(&self.instruction, &self.format, &self.array);
493 write!(f, "{}", writer.finish())
494 }
495}