simple_text_editor/
ops.rs

1/// Contains all possible operations, as interpreted from commands via input.
2/// `Invalid` is a catch-all for any unrecognized commands.
3#[derive(Clone, Debug, PartialEq)]
4pub enum Operation<'a> {
5    /// The append command, denotated by `1` in the program's input. The associated `&'a str`
6    /// contains the string data to be appended to the buffer.
7    /// ```
8    /// use simple_text_editor::ops::*;
9    /// let op = "1 append this text".into();
10    /// match op {
11    ///     Operation::Append(val) => {
12    ///         assert_eq!(val, "append this text");
13    ///     }
14    ///     _ => panic!("should have matched Operation::Append"),
15    /// }
16    /// ```
17    Append(&'a str),
18    /// The delete command, denotated by `2` in the program's input. The associated `usize` is the
19    /// number of characters to be delete from the back of the buffer.
20    /// ```
21    /// use simple_text_editor::ops::*;
22    /// let op = "2 5".into();
23    /// match op {
24    ///     Operation::Delete(n) => {
25    ///         assert_eq!(n, 5);
26    ///     }
27    ///     _ => panic!("should have matched Operation::Delete"),
28    /// }
29    /// ```
30    Delete(usize),
31    /// The print command, denotated by `3` in the program's input. The associated `usize` is the
32    /// 1-based index at which the character from the buffer should be printed.
33    /// ```
34    /// use simple_text_editor::ops::*;
35    /// let op = "3 1".into();
36    /// match op {
37    ///     Operation::Print(i) => {
38    ///         assert_eq!(i, 1);
39    ///     }
40    ///     _ => panic!("should have matched Operation::Print"),
41    /// }
42    /// ```
43    Print(usize),
44    /// The undo command, denotated by `4` in the program's input. There is no associated data, and
45    /// thus simply pops a command from a maintained stack of undo-eligible operations, either being
46    /// append or delete.
47    /// ```
48    /// use simple_text_editor::ops::*;
49    /// let op = "4".into();
50    /// match op {
51    ///     Operation::Undo => {
52    ///         assert!(true);
53    ///     }
54    ///     _ => panic!("should have matched Operation::Undo"),
55    /// }
56    /// ```
57    Undo,
58    /// Invalid is a catch-all for any unrecognized commands, and is ignored by the program.
59    /// ```
60    /// use simple_text_editor::ops::*;
61    /// let op = "__BADOPERATION__".into();
62    /// match op {
63    ///     Operation::Invalid => {
64    ///         assert!(true);
65    ///     }
66    ///     _ => panic!("should have matched Operation::Invalid"),
67    /// }
68    /// ```
69    Invalid,
70}
71
72/// Convert a line of input into an Operation.
73/// ```
74/// use simple_text_editor::ops::*;
75/// let op = "1 abc".into();
76/// assert!(matches!(op, Operation::Append("abc")));
77/// ```
78impl<'a> From<&'a str> for Operation<'a> {
79    fn from(input: &'a str) -> Self {
80        let input = input.trim_start();
81        if input.is_empty() {
82            return Operation::Invalid;
83        }
84
85        let mut chars = input.chars();
86        match chars.next().map(|c| (c, chars.as_str())) {
87            Some(('1', val)) => Operation::Append(remove_sep_space(val)),
88            Some((op @ '2', val)) | Some((op @ '3', val)) => {
89                parse_delete_or_print(op, remove_sep_space(val))
90            }
91            Some(('4', _)) => Operation::Undo,
92            _ => Operation::Invalid,
93        }
94    }
95}
96
97/// Remove the whitespace between op and value, but not any leading whitespace of the value
98fn remove_sep_space(val: &str) -> &str {
99    let mut chars = val.chars();
100    if let Some((_, rest)) = chars.next().map(|c| (c, chars.as_str())) {
101        rest
102    } else {
103        val
104    }
105}
106
107/// Combine parsing logic from input when dealing with delete or print operations.
108fn parse_delete_or_print(op: char, value_to_parse: &str) -> Operation {
109    if let Ok(val) = value_to_parse.trim().parse::<usize>() {
110        match op {
111            '2' => Operation::Delete(val),
112            '3' => Operation::Print(val),
113            _ => Operation::Invalid,
114        }
115    } else {
116        Operation::Invalid
117    }
118}
119
120/// Parse all operations from combined input, including the command count (first line).
121/// ```
122/// use simple_text_editor::ops::*;
123/// assert_eq!(
124///     parse(
125///         r#"4
126/// 1 abc
127/// 2 1
128/// 3 1
129/// 4
130/// "#
131///     ),
132///     Some((
133///         4 as usize,
134///         vec![
135///             Operation::Append("abc"),
136///             Operation::Delete(1),
137///             Operation::Print(1),
138///             Operation::Undo,
139///         ]
140///     ))
141/// );
142/// ```
143pub fn parse(input: &str) -> Option<(usize, Vec<Operation>)> {
144    let lines = input.lines();
145    let num_ops = lines.clone().take(1).next().unwrap_or_default().parse();
146    if num_ops.is_err() {
147        return None;
148    }
149    let ops = lines.skip(1).map(|line| line.into()).collect();
150    Some((num_ops.unwrap(), ops))
151}
152
153/// Contains all possible operations which are eligible for "undo".
154/// `Append` and `Delete` maintain the inverse of their `Operation` type counterparts,
155/// and hold the data necessary to undo the operation on the buffer.
156#[derive(Debug)]
157pub enum UndoableOperation {
158    /// The undo operation for a previously executed append command. The associated `usize` is the
159    /// count of characters which had been appended, and should now be deleted from the back of
160    /// the buffer.
161    Append(usize),
162    /// The undo operation for a previously executed delete command. The associated `Vec<u8>` is the
163    /// set of characters which had been previously popped from the back of the buffer, and should
164    /// now be re-appended. Note, the characters are in reverse-order, which is the natural order
165    /// after they had been popped from the buffer. As an optimization, the program will lazily
166    /// re-order these to be pushed onto the buffer so they are accurately re-appended.
167    Delete(Vec<u8>),
168}