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}