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}