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}