Skip to main content

inkling/node/
node.rs

1//! Node tree structure for branching content.
2
3use crate::{
4    error::{parse::validate::ValidationError, utils::MetaData},
5    knot::Address,
6    line::{InternalChoice, InternalLine},
7    story::validate::{ValidateContent, ValidationData},
8};
9
10#[cfg(feature = "serde_support")]
11use serde::{Deserialize, Serialize};
12
13#[derive(Clone, Debug)]
14#[cfg_attr(feature = "serde_support", derive(Deserialize, Serialize))]
15/// Root of a single `Stitch`, containing all text and branching content belonging to it.
16pub struct RootNode {
17    /// Address of stitch that this node belongs to.
18    pub address: Address,
19    /// Content grouped under this stitch.
20    pub items: Vec<NodeItem>,
21}
22
23#[derive(Clone, Debug)]
24#[cfg_attr(feature = "serde_support", derive(Deserialize, Serialize))]
25/// Branch from a set of choices in a `Stitch`.
26///
27/// Largely identical to `RootNode` but also contains the data associated with
28/// the choice leading to it.
29pub struct Branch {
30    /// Choice which represents selecting this branch from a set.
31    pub choice: InternalChoice,
32    /// Content grouped under this branch.
33    pub items: Vec<NodeItem>,
34    /// Number of times the node has been visited in the story.
35    pub num_visited: u32,
36}
37
38#[derive(Clone, Debug)]
39#[cfg_attr(feature = "serde_support", derive(Deserialize, Serialize))]
40/// Every item that a `Stitch` contains can be either some text producing asset
41/// or a branching point which the user must select an option from to continue.
42pub enum NodeItem {
43    Line(InternalLine),
44    BranchingPoint(Vec<Branch>),
45}
46
47#[cfg(test)]
48/// Simplified checking of which match a `NodeItem` is during testing.
49impl NodeItem {
50    pub fn is_branching_choice(&self) -> bool {
51        match self {
52            NodeItem::BranchingPoint(..) => true,
53            _ => false,
54        }
55    }
56
57    pub fn is_line(&self) -> bool {
58        match self {
59            NodeItem::Line(..) => true,
60            _ => false,
61        }
62    }
63}
64
65impl ValidateContent for RootNode {
66    fn validate(
67        &mut self,
68        error: &mut ValidationError,
69        current_location: &Address,
70        meta_data: &MetaData,
71        data: &ValidationData,
72    ) {
73        self.items
74            .iter_mut()
75            .for_each(|item| item.validate(error, current_location, meta_data, data))
76    }
77}
78
79impl ValidateContent for Branch {
80    fn validate(
81        &mut self,
82        error: &mut ValidationError,
83        current_location: &Address,
84        meta_data: &MetaData,
85        data: &ValidationData,
86    ) {
87        let num_errors = error.num_errors();
88
89        self.choice
90            .validate(error, current_location, meta_data, data);
91
92        // The first line of these items is the selection text from the choice. If we found
93        // errors when evaluating that we do not want to add copies of them:
94        // instead, we skip it if (but *only* if) an error was found. If no errors were found
95        // we have to ensure that addresses are validated in it.
96        if let Some((choice_display_line, items)) = self.items.split_first_mut() {
97            if num_errors == error.num_errors() {
98                choice_display_line.validate(error, current_location, meta_data, data);
99            }
100
101            items
102                .iter_mut()
103                .for_each(|item| item.validate(error, current_location, meta_data, data));
104        }
105    }
106}
107
108impl ValidateContent for NodeItem {
109    fn validate(
110        &mut self,
111        error: &mut ValidationError,
112        current_location: &Address,
113        meta_data: &MetaData,
114        data: &ValidationData,
115    ) {
116        match self {
117            NodeItem::BranchingPoint(branches) => branches
118                .iter_mut()
119                .for_each(|item| item.validate(error, current_location, meta_data, data)),
120            NodeItem::Line(line) => line.validate(error, current_location, meta_data, data),
121        };
122    }
123}
124
125pub mod builders {
126    //! Builders for constructing nodes.
127    //!
128    //! For testing purposes most of these structs implement additional functions when
129    //! the `test` profile is activated. These functions are not meant to be used internally
130    //! except by tests, since they do not perform any validation of the content.
131
132    use super::{Branch, NodeItem, RootNode};
133
134    use crate::{
135        knot::{Address, AddressKind},
136        line::{InternalChoice, InternalLine},
137    };
138
139    #[cfg(test)]
140    use crate::line::LineChunk;
141
142    /// Builder for a `RootNote`.
143    ///
144    /// # Notes
145    ///  *  Sets the number of visits to 0
146    pub struct RootNodeBuilder {
147        address: Address,
148        items: Vec<NodeItem>,
149    }
150
151    impl RootNodeBuilder {
152        pub fn from_address(knot: &str, stitch: &str) -> Self {
153            let address = Address::Validated(AddressKind::Location {
154                knot: knot.to_string(),
155                stitch: stitch.to_string(),
156            });
157
158            RootNodeBuilder {
159                address,
160                items: Vec::new(),
161            }
162        }
163
164        pub fn build(self) -> RootNode {
165            RootNode {
166                address: self.address,
167                items: self.items,
168            }
169        }
170
171        pub fn add_branching_choice(&mut self, branching_set: Vec<Branch>) {
172            self.add_item(NodeItem::BranchingPoint(branching_set));
173        }
174
175        pub fn add_item(&mut self, item: NodeItem) {
176            self.items.push(item);
177        }
178
179        pub fn add_line(&mut self, line: InternalLine) {
180            self.add_item(NodeItem::Line(line));
181        }
182
183        #[cfg(test)]
184        pub fn empty() -> Self {
185            Self::from_address("", "")
186        }
187
188        #[cfg(test)]
189        pub fn with_item(mut self, item: NodeItem) -> Self {
190            self.items.push(item);
191            self
192        }
193
194        #[cfg(test)]
195        pub fn with_branching_choice(self, branching_choice_set: NodeItem) -> Self {
196            self.with_item(branching_choice_set)
197        }
198
199        #[cfg(test)]
200        pub fn with_line_chunk(self, chunk: LineChunk) -> Self {
201            self.with_item(NodeItem::Line(InternalLine::from_chunk(chunk)))
202        }
203
204        #[cfg(test)]
205        pub fn with_text_line_chunk(self, content: &str) -> Self {
206            self.with_item(NodeItem::Line(InternalLine::from_string(content)))
207        }
208    }
209
210    /// Builder for a `Branch`.
211    ///
212    /// Is created from the `InternalChoice` that spawns the branch in the parsed lines
213    /// of text content.
214    ///
215    /// # Notes
216    ///  *  Adds the line from its choice as the first in its item list.
217    ///  *  Sets the number of visits to 0.
218    pub struct BranchBuilder {
219        choice: InternalChoice,
220        items: Vec<NodeItem>,
221    }
222
223    impl BranchBuilder {
224        pub fn from_choice(choice: InternalChoice) -> Self {
225            let line = choice.display_text.clone();
226
227            BranchBuilder {
228                choice,
229                items: vec![NodeItem::Line(line)],
230            }
231        }
232
233        pub fn build(self) -> Branch {
234            Branch {
235                choice: self.choice,
236                items: self.items,
237                num_visited: 0,
238            }
239        }
240
241        pub fn add_branching_choice(&mut self, branching_set: Vec<Branch>) {
242            self.add_item(NodeItem::BranchingPoint(branching_set));
243        }
244
245        pub fn add_item(&mut self, item: NodeItem) {
246            self.items.push(item);
247        }
248
249        pub fn add_line(&mut self, line: InternalLine) {
250            self.add_item(NodeItem::Line(line));
251        }
252
253        #[cfg(test)]
254        pub fn with_item(mut self, item: NodeItem) -> Self {
255            self.items.push(item);
256            self
257        }
258
259        #[cfg(test)]
260        pub fn with_branching_choice(self, branching_choice_set: NodeItem) -> Self {
261            self.with_item(branching_choice_set)
262        }
263
264        #[cfg(test)]
265        pub fn with_text_line_chunk(self, content: &str) -> Self {
266            self.with_item(NodeItem::Line(InternalLine::from_string(content)))
267        }
268    }
269
270    #[cfg(test)]
271    pub struct BranchingPointBuilder {
272        items: Vec<Branch>,
273    }
274
275    #[cfg(test)]
276    impl BranchingPointBuilder {
277        pub fn new() -> Self {
278            BranchingPointBuilder { items: Vec::new() }
279        }
280
281        pub fn with_branch(mut self, choice: Branch) -> Self {
282            self.items.push(choice);
283            self
284        }
285
286        pub fn build(self) -> NodeItem {
287            NodeItem::BranchingPoint(self.items)
288        }
289    }
290}