ass_editor/core/document/builder_edit.rs
1//! Builder-driven structured event editing
2//!
3//! Implements `edit_event_with_builder`, which pre-populates an
4//! `EventBuilder` from an existing event, applies the caller's fluent
5//! modifications, and rewrites the line honoring the section format.
6
7use super::EditorDocument;
8use crate::core::errors::{EditorError, Result};
9use crate::core::position::{Position, Range};
10use ass_core::parser::ast::Section;
11
12#[cfg(not(feature = "std"))]
13use alloc::{
14 format,
15 string::{String, ToString},
16 vec::Vec,
17};
18
19impl EditorDocument {
20 /// Edit event using a builder for structured modifications
21 ///
22 /// Allows editing events using the EventBuilder fluent API. The builder
23 /// is pre-populated with the current event's values, allowing selective
24 /// field updates.
25 ///
26 /// # Arguments
27 ///
28 /// * `index` - Zero-based index of the event to edit
29 /// * `builder_fn` - Function that receives a pre-populated EventBuilder
30 ///
31 /// # Example
32 ///
33 /// ```rust
34 /// # use ass_editor::core::EditorDocument;
35 /// # let content = r#"[Script Info]
36 /// # Title: Test
37 /// #
38 /// # [Events]
39 /// # Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
40 /// # Dialogue: 0,0:00:00.00,0:00:05.00,Default,,0,0,0,,Original text"#;
41 /// # let mut doc = EditorDocument::from_content(content).unwrap();
42 /// # use ass_editor::core::builders::EventBuilder;
43 /// doc.edit_event_with_builder(0, |builder| {
44 /// builder
45 /// .text("New dialogue text")
46 /// .style("NewStyle")
47 /// .end_time("0:00:10.00")
48 /// })?;
49 /// # Ok::<(), Box<dyn std::error::Error>>(())
50 /// ```
51 pub fn edit_event_with_builder<F>(&mut self, index: usize, builder_fn: F) -> Result<String>
52 where
53 F: for<'a> FnOnce(
54 crate::core::builders::EventBuilder,
55 ) -> crate::core::builders::EventBuilder,
56 {
57 use crate::core::builders::EventBuilder;
58
59 let content = self.text();
60 let mut event_info = None;
61 let mut event_count = 0;
62 let mut format_line = None;
63
64 // Find the event and extract format line
65 self.parse_script_with(|script| -> Result<()> {
66 for section in script.sections() {
67 if let Section::Events(events) = section {
68 // Get format line if available
69 if format_line.is_none() {
70 // Find Events section header and format line in raw text
71 if let Some(events_pos) = content.find("[Events]") {
72 let after_header = &content[events_pos + 8..];
73 if let Some(format_pos) = after_header.find("Format:") {
74 let format_start = events_pos + 8 + format_pos + 7; // Skip "Format:"
75 if let Some(format_end) = content[format_start..].find('\n') {
76 let format_str =
77 content[format_start..format_start + format_end].trim();
78 let fields: Vec<&str> =
79 format_str.split(',').map(str::trim).collect();
80 format_line = Some(fields);
81 }
82 }
83 }
84 }
85
86 for event in events {
87 if event_count == index {
88 // Create a builder pre-populated with current values
89 let mut builder = match event.event_type {
90 ass_core::parser::ast::EventType::Dialogue => {
91 EventBuilder::dialogue()
92 }
93 ass_core::parser::ast::EventType::Comment => {
94 EventBuilder::comment()
95 }
96 _ => EventBuilder::new(),
97 };
98
99 // Pre-populate builder with current event values
100 builder = builder
101 .layer(event.layer.parse::<u32>().unwrap_or(0))
102 .start_time(event.start)
103 .end_time(event.end)
104 .style(event.style)
105 .speaker(event.name)
106 .margin_left(event.margin_l.parse::<u32>().unwrap_or(0))
107 .margin_right(event.margin_r.parse::<u32>().unwrap_or(0))
108 .margin_vertical(event.margin_v.parse::<u32>().unwrap_or(0))
109 .effect(event.effect)
110 .text(event.text);
111
112 if let Some(margin_t) = event.margin_t {
113 builder = builder.margin_top(margin_t.parse::<u32>().unwrap_or(0));
114 }
115 if let Some(margin_b) = event.margin_b {
116 builder =
117 builder.margin_bottom(margin_b.parse::<u32>().unwrap_or(0));
118 }
119
120 // Apply user modifications
121 let modified_builder = builder_fn(builder);
122
123 // Build the new event line
124 let new_line = if let Some(ref format_fields) = format_line {
125 modified_builder.build_with_format(format_fields)?
126 } else {
127 modified_builder.build()?
128 };
129
130 // Find the event line in the raw text
131 let event_type_str = event.event_type.as_str();
132 let pattern = format!(
133 "{}: {},{},{}",
134 event_type_str, event.layer, event.start, event.end
135 );
136
137 let event_line = if let Some(pos) = content.find(&pattern) {
138 let line_end = content[pos..]
139 .find('\n')
140 .map(|n| pos + n)
141 .unwrap_or(content.len());
142 (pos, line_end)
143 } else {
144 return Err(EditorError::ValidationError {
145 message: "Could not find event line in document".to_string(),
146 });
147 };
148
149 event_info = Some((event_line, new_line));
150 return Ok(());
151 }
152 event_count += 1;
153 }
154 }
155 }
156 Ok(())
157 })??;
158
159 if let Some(((line_start, line_end), new_line)) = event_info {
160 // Replace the line in the document
161 let range = Range::new(Position::new(line_start), Position::new(line_end));
162 self.replace(range, &new_line)?;
163
164 Ok(new_line)
165 } else {
166 Err(EditorError::InvalidRange {
167 start: index,
168 end: index + 1,
169 length: event_count,
170 })
171 }
172 }
173}