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}