Skip to main content

ass_core/parser/script/
batch.rs

1//! Bulk and transactional editing operations.
2//!
3//! Implements batched line updates, batched style/event insertion, and the
4//! [`Script::atomic_batch_update`] driver that validates every operation before
5//! applying changes to a cloned script for all-or-nothing semantics.
6
7use alloc::vec::Vec;
8
9use crate::parser::ast::Section;
10use crate::parser::errors::ParseError;
11
12use super::types::{BatchUpdateResult, EventBatch, StyleBatch, UpdateOperation};
13use super::Script;
14
15impl<'a> Script<'a> {
16    /// Perform multiple line updates in a single operation
17    ///
18    /// Updates are performed in the order provided. If an update fails,
19    /// it's recorded in the failed list but doesn't stop other updates.
20    ///
21    /// # Arguments
22    ///
23    /// * `operations` - List of update operations to perform
24    ///
25    /// # Returns
26    ///
27    /// Result containing successful updates and failures
28    pub fn batch_update_lines(
29        &mut self,
30        operations: Vec<UpdateOperation<'a>>,
31    ) -> BatchUpdateResult<'a> {
32        let mut result = BatchUpdateResult {
33            updated: Vec::with_capacity(operations.len()),
34            failed: Vec::new(),
35        };
36
37        // Sort operations by offset to process in order
38        let mut sorted_ops = operations;
39        sorted_ops.sort_by_key(|op| op.offset);
40
41        for op in sorted_ops {
42            match self.update_line_at_offset(op.offset, op.new_line, op.line_number) {
43                Ok(old_content) => {
44                    result.updated.push((op.offset, old_content));
45                }
46                Err(e) => {
47                    result.failed.push((op.offset, e));
48                }
49            }
50        }
51
52        result
53    }
54
55    /// Add multiple styles in a single operation
56    ///
57    /// Creates the styles section if it doesn't exist.
58    ///
59    /// # Arguments
60    ///
61    /// * `batch` - Batch of styles to add
62    ///
63    /// # Returns
64    ///
65    /// Indices of the added styles within the styles section
66    pub fn batch_add_styles(&mut self, batch: StyleBatch<'a>) -> Vec<usize> {
67        let mut indices = Vec::with_capacity(batch.styles.len());
68
69        // Find or create styles section
70        let styles_section_index = self
71            .sections
72            .iter()
73            .position(|s| matches!(s, Section::Styles(_)));
74
75        if let Some(index) = styles_section_index {
76            if let Section::Styles(styles) = &mut self.sections[index] {
77                let start_index = styles.len();
78                styles.extend(batch.styles);
79                indices.extend(start_index..styles.len());
80            }
81        } else {
82            // Create new styles section
83            let count = batch.styles.len();
84            self.sections.push(Section::Styles(batch.styles));
85            indices.extend(0..count);
86        }
87
88        indices
89    }
90
91    /// Add multiple events in a single operation
92    ///
93    /// Creates the events section if it doesn't exist.
94    ///
95    /// # Arguments
96    ///
97    /// * `batch` - Batch of events to add
98    ///
99    /// # Returns
100    ///
101    /// Indices of the added events within the events section
102    pub fn batch_add_events(&mut self, batch: EventBatch<'a>) -> Vec<usize> {
103        let mut indices = Vec::with_capacity(batch.events.len());
104
105        // Find or create events section
106        let events_section_index = self
107            .sections
108            .iter()
109            .position(|s| matches!(s, Section::Events(_)));
110
111        if let Some(index) = events_section_index {
112            if let Section::Events(events) = &mut self.sections[index] {
113                let start_index = events.len();
114                events.extend(batch.events);
115                indices.extend(start_index..events.len());
116            }
117        } else {
118            // Create new events section
119            let count = batch.events.len();
120            self.sections.push(Section::Events(batch.events));
121            indices.extend(0..count);
122        }
123
124        indices
125    }
126
127    /// Apply a batch of mixed operations atomically
128    ///
129    /// All operations are validated first. If any validation fails,
130    /// no changes are made. This provides transactional semantics.
131    ///
132    /// # Arguments
133    ///
134    /// * `updates` - Line updates to perform
135    /// * `style_additions` - Styles to add
136    /// * `event_additions` - Events to add
137    ///
138    /// # Returns
139    ///
140    /// Ok if all operations succeed, Err with the first validation error
141    ///
142    /// # Errors
143    ///
144    /// Returns error if any operation would fail, without making changes
145    pub fn atomic_batch_update(
146        &mut self,
147        updates: Vec<UpdateOperation<'a>>,
148        style_additions: Option<StyleBatch<'a>>,
149        event_additions: Option<EventBatch<'a>>,
150    ) -> core::result::Result<(), ParseError> {
151        // First, validate all updates
152        for op in &updates {
153            // Check if offset is valid
154            let section_found = self.sections.iter().any(|s| {
155                s.span()
156                    .is_some_and(|span| span.start <= op.offset && op.offset < span.end)
157            });
158            if !section_found {
159                return Err(ParseError::SectionNotFound);
160            }
161
162            // Try parsing the line
163            self.parse_line_auto(op.new_line, op.line_number)?;
164        }
165
166        // All validations passed, now apply changes
167        // Clone self to preserve original state in case of failure
168        let mut temp_script = self.clone();
169
170        // Apply updates
171        for op in updates {
172            temp_script.update_line_at_offset(op.offset, op.new_line, op.line_number)?;
173        }
174
175        // Apply style additions
176        if let Some(styles) = style_additions {
177            temp_script.batch_add_styles(styles);
178        }
179
180        // Apply event additions
181        if let Some(events) = event_additions {
182            temp_script.batch_add_events(events);
183        }
184
185        // All operations succeeded, commit changes
186        *self = temp_script;
187        Ok(())
188    }
189}