aimx/aim/
version.rs

1//! Version header parsing and management for AIM files
2//!
3//! This module provides functionality for parsing and managing version headers in AIM files.
4//! AIM uses a two-component versioning system consisting of an epoch (major version) and
5//! an optional partial component (minor version) for tracking changes to workflows.
6//!
7//! # Version Format
8//!
9//! Version headers in AIM files follow the format `[epoch]` or `[epoch:partial]`:
10//!
11//! - `[1]` - Version with epoch 1 and implicit partial 0
12//! - `[1:2]` - Version with epoch 1 and partial 2
13//! - `[ 123 ]` - Version with epoch 123 and implicit partial 0 (whitespace allowed)
14//!
15//! # Examples
16//!
17//! ```
18//! use aimx::aim::Version;
19//!
20//! // Create and manipulate versions
21//! let mut version = Version::new(1, 0);
22//! assert_eq!(version.epoch(), 1);
23//! assert_eq!(version.partial(), 0);
24//!
25//! version.increment_partial();
26//! assert_eq!(version.partial(), 1);
27//!
28//! version.increment_epoc();
29//! assert_eq!(version.epoch(), 2);
30//! assert_eq!(version.partial(), 0); // Reset to 0
31//! ```
32
33use anyhow::{anyhow, Result};
34use nom::{
35    Finish, IResult, Parser,
36    character::complete::{char, multispace0},
37    combinator::opt,
38    sequence::delimited,
39};
40use crate::literals::parse_unsigned;
41
42/// Provides journal version support within the AIM file. The version header represents a version
43/// with an epoch and optional partial component.
44///
45/// A version consists of two parts:
46/// - **epoch**: The major version number that increments when creating new versions
47/// - **partial**: The minor version number that increments for partial updates within the same epoch
48///
49/// This versioning system is used throughout AIM to track changes and enable efficient
50/// version control. The epoch represents major structural changes while partial versions
51/// represent incremental updates within the same epoch.
52///
53/// # Examples
54///
55/// ```
56/// use aimx::aim::Version;
57///
58/// // Create a new version
59/// let version = Version::new(1, 0);
60/// assert_eq!(version.epoch(), 1);
61/// assert_eq!(version.partial(), 0);
62///
63/// // Create a version with partial
64/// let version_with_partial = Version::new(2, 3);
65/// assert_eq!(version_with_partial.epoch(), 2);
66/// assert_eq!(version_with_partial.partial(), 3);
67/// ```
68#[derive(Debug, PartialEq, Clone)]
69pub struct Version {
70    epoc: u32,
71    partial: u32,
72}
73
74impl Version {
75    /// Creates a new Version with the specified epoch and partial values.
76    ///
77    /// # Parameters
78    ///
79    /// * `epoc` - The major version number
80    /// * `partial` - The minor version number (typically 0 for new epochs)
81    ///
82    /// # Returns
83    ///
84    /// A new `Version` instance with the specified values.
85    ///
86    /// # Examples
87    ///
88    /// ```
89    /// use aimx::aim::Version;
90    ///
91    /// let version = Version::new(1, 0);
92    /// assert_eq!(version.epoc(), 1);
93    /// assert_eq!(version.partial(), 0);
94    /// ```
95    pub fn new(epoc: u32, partial: u32) -> Self {
96        Self {
97            epoc,
98            partial
99        }
100    }
101
102    /// Returns the epoch (major version) of this version.
103    ///
104    /// # Returns
105    ///
106    /// The epoch value as a `u32`.
107    ///
108    /// # Examples
109    ///
110    /// ```
111    /// use aimx::aim::Version;
112    ///
113    /// let version = Version::new(2, 5);
114    /// assert_eq!(version.epoc(), 2);
115    /// ```
116    pub fn epoc(&self) -> u32 {
117        self.epoc
118    }
119
120    /// Returns the epoch (major version) of this version.
121    ///
122    /// This is an alias for [`Self::epoc()`] for better readability.
123    ///
124    /// # Returns
125    ///
126    /// The epoch value as a `u32`.
127    ///
128    /// # Examples
129    ///
130    /// ```
131    /// use aimx::aim::Version;
132    ///
133    /// let version = Version::new(2, 5);
134    /// assert_eq!(version.epoch(), 2);
135    /// ```
136    pub fn epoch(&self) -> u32 {
137        self.epoc
138    }
139
140    /// Sets the epoch and resets the partial to 0.
141    ///
142    /// This method is typically used when moving to a new major version.
143    /// Setting the epoch automatically resets the partial to 0 since
144    /// partial versions are specific to their epoch.
145    ///
146    /// # Parameters
147    ///
148    /// * `epoc` - The new epoch value
149    ///
150    /// # Examples
151    ///
152    /// ```
153    /// use aimx::aim::Version;
154    ///
155    /// let mut version = Version::new(1, 5);
156    /// version.set_epoc(2);
157    /// assert_eq!(version.epoc(), 2);
158    /// assert_eq!(version.partial(), 0); // Reset to 0
159    /// ```
160    pub fn set_epoc(&mut self, epoc: u32) {
161        self.epoc = epoc;
162        self.partial = 0;
163    }
164
165    /// Increments the epoch and resets the partial to 0.
166    ///
167    /// This method is used to advance to the next major version.
168    /// The partial is automatically reset to 0 since partial versions
169    /// are specific to their epoch.
170    ///
171    /// # Examples
172    ///
173    /// ```
174    /// use aimx::aim::Version;
175    ///
176    /// let mut version = Version::new(1, 5);
177    /// version.increment_epoc();
178    /// assert_eq!(version.epoc(), 2);
179    /// assert_eq!(version.partial(), 0); // Reset to 0
180    /// ```
181    pub fn increment_epoc(&mut self) {
182        self.epoc += 1;
183        self.partial = 0;
184    }
185
186    /// Returns the partial (minor version) of this version.
187    ///
188    /// # Returns
189    ///
190    /// The partial value as a `u32`.
191    ///
192    /// # Examples
193    ///
194    /// ```
195    /// use aimx::aim::Version;
196    ///
197    /// let version = Version::new(2, 5);
198    /// assert_eq!(version.partial(), 5);
199    /// ```
200    pub fn partial(&self) -> u32 {
201        self.partial
202    }
203
204    /// Sets the partial value.
205    ///
206    /// This method allows setting the partial version directly without
207    /// affecting the epoch. Use this when you need to set a specific
208    /// partial version within the same epoch.
209    ///
210    /// # Parameters
211    ///
212    /// * `partial` - The new partial value
213    ///
214    /// # Examples
215    ///
216    /// ```
217    /// use aimx::aim::Version;
218    ///
219    /// let mut version = Version::new(1, 0);
220    /// version.set_partial(3);
221    /// assert_eq!(version.epoc(), 1); // Unchanged
222    /// assert_eq!(version.partial(), 3);
223    /// ```
224    pub fn set_partial(&mut self, partial: u32) {
225        self.partial = partial;
226    }
227
228    /// Increments the partial value by 1.
229    ///
230    /// This method is used to advance to the next partial version
231    /// within the same epoch. This is commonly used for incremental
232    /// updates that don't warrant a new epoch.
233    ///
234    /// # Examples
235    ///
236    /// ```
237    /// use aimx::aim::Version;
238    ///
239    /// let mut version = Version::new(1, 2);
240    /// version.increment_partial();
241    /// assert_eq!(version.epoc(), 1); // Unchanged
242    /// assert_eq!(version.partial(), 3);
243    /// ```
244    pub fn increment_partial(&mut self) {
245        self.partial += 1;
246    }
247}
248
249
250/// Parses a version header from AIM markup format.
251///
252/// This function parses version headers in the format `[epoch]` or `[epoch:partial]`
253/// as used in AIM files. The version header is surrounded by square brackets
254/// and may contain optional whitespace around the entire header.
255///
256/// # Format
257///
258/// - `[1]` - Version with epoch 1 and implicit partial 0
259/// - `[1:2]` - Version with epoch 1 and partial 2
260/// - `[ 123 ]` - Version with epoch 123 and implicit partial 0 (whitespace allowed)
261/// - `[ 1:2 ]` - Version with epoch 1 and partial 2 (whitespace allowed)
262///
263/// # Parameters
264///
265/// * `input` - The string containing the version header to parse
266///
267/// # Returns
268///
269/// * `Ok(Version)` - The parsed version with epoch and optional partial
270/// * `Err(anyhow::Error)` - Parse error with descriptive message
271///
272/// # Examples
273///
274/// ```
275/// use aimx::aim::parse_version;
276///
277/// // Parse simple version
278/// let version = parse_version("[1]").unwrap();
279/// assert_eq!(version.epoc(), 1);
280/// assert_eq!(version.partial(), 0);
281///
282/// // Parse version with partial
283/// let version = parse_version("[2:3]").unwrap();
284/// assert_eq!(version.epoc(), 2);
285/// assert_eq!(version.partial(), 3);
286///
287/// // Parse with whitespace around header
288/// let version = parse_version("[ 45 ]").unwrap();
289/// assert_eq!(version.epoc(), 45);
290/// assert_eq!(version.partial(), 0);
291///
292/// // Invalid formats return errors
293/// assert!(parse_version("1").is_err()); // Missing brackets
294/// assert!(parse_version("[1:]").is_err()); // Missing partial
295/// assert!(parse_version("[]").is_err()); // Missing version
296/// ```
297pub fn parse_version(input: &str) -> Result<Version> {
298    match delimited(
299        char('['),
300        delimited(multispace0, parse_epoc, multispace0),
301        char(']'),
302    )
303    .parse(input).finish() {
304        Ok((_, version)) => Ok(version),
305        Err(e) => Err(anyhow!("Parse error: {}", e)),
306    }
307}
308
309/// Parses an epoch with optional partial component.
310///
311/// Internal parser function that handles the format `epoch` or `epoch:partial`.
312/// The partial component is optional and defaults to 0 if not present.
313fn parse_epoc(input: &str) -> IResult<&str, Version> {
314    let (input, (epoc, partial_opt)) =
315        (parse_unsigned, opt((char(':'), parse_unsigned))).parse(input)?;
316    let partial = partial_opt.map(|(_, p)| p).unwrap_or(0);
317    
318    Ok((input, Version { epoc, partial }))
319}