Skip to main content

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}