1use std::collections::VecDeque;
6
7use pest::{iterators::Pair, Parser};
8use pest_derive::Parser;
9
10use crate::{
11 error::Error,
12 line::*,
13 context::*,
14 patch::*,
15 PatchResult,
16};
17
18#[derive(Parser)]
19#[grammar = "../peg/patch.peg"]
20pub struct PatchProcessor {
21 text: Vec<String>,
22 patch: Patch,
23}
24
25impl PatchProcessor {
26 pub fn converted(text: Vec<String>, patch: &str) -> PatchResult<Self> {
27 Ok(Self {
28 text,
29 patch: Self::convert(patch)?,
30 })
31 }
32
33 pub fn process(&self) -> PatchResult<Vec<String>> {
34 let mut file2_text = Vec::new();
35 let mut file1_ptr: usize = 0;
36
37 for context in &self.patch.contexts {
38 for i in file1_ptr..context.header.file1_l-1 {
39 file2_text.push(
40 self.text
41 .get(i)
42 .ok_or_else(|| Error::AbruptInput(i))?
43 .to_owned(),
44 );
45 }
46 file1_ptr = context.header.file1_l-1;
47 for line in &context.data {
48 match line {
49 Line::Context(ref data) => {
50 if self
51 .text
52 .get(file1_ptr)
53 .ok_or_else(|| Error::AbruptInput(file1_ptr))?
54 != data
55 {
56 return Err(Error::PatchInputMismatch(file1_ptr));
57 }
58 file2_text.push(data.to_owned());
59 file1_ptr += 1;
60 }
61 Line::Delete(ref data) => {
62 if self
63 .text
64 .get(file1_ptr)
65 .ok_or_else(|| Error::AbruptInput(file1_ptr))?
66 != data
67 {
68 return Err(Error::PatchInputMismatch(file1_ptr));
69 }
70 file1_ptr += 1;
71 }
72 Line::Insert(ref data) => {
73 file2_text.push(data.to_owned());
74 }
75 }
76 }
77 }
78
79 for i in file1_ptr..self.text.len() {
80 file2_text.push(
81 self.text
82 .get(i)
83 .ok_or_else(|| Error::AbruptInput(i))?
84 .to_owned(),
85 );
86 }
87
88 Ok(file2_text)
89 }
90
91 pub fn convert(patch: &str) -> PatchResult<Patch> {
92 let peg_patch = Self::parse(Rule::patch, patch)?
93 .next()
94 .ok_or(Error::NotFound("patch"))?;
95
96 let mut contexts = VecDeque::new();
97 let mut input = None;
98 let mut output = None;
99
100 for patch_element in peg_patch.into_inner() {
101 match patch_element.as_rule() {
102 Rule::file1_header => {
103 for header_element in patch_element.into_inner() {
104 if let Rule::path = header_element.as_rule() {
105 input = Some(header_element.as_span().as_str().to_owned());
106 }
107 }
108 }
109 Rule::file2_header => {
110 for header_element in patch_element.into_inner() {
111 if let Rule::path = header_element.as_rule() {
112 output = Some(header_element.as_span().as_str().to_owned());
113 }
114 }
115 }
116 Rule::context => {
117 let mut peg_context = patch_element.into_inner();
118 let context_header = peg_context
119 .next()
120 .ok_or(Error::NotFound("context_header"))?;
121 let context_header = if let Rule::context_header = context_header.as_rule() {
122 Self::get_context_header(context_header)?
123 } else {
124 return Err(Error::MalformedPatch(
125 "Context header is not at the start of a context",
126 ));
127 };
128
129 let mut context = Context {
130 header: context_header,
131 data: Vec::new(),
132 };
133 for line in peg_context {
134 match line.as_rule() {
135 Rule::line_context => context
136 .data
137 .push(Line::Context(line.as_span().as_str().to_owned())),
138 Rule::line_deleted => context
139 .data
140 .push(Line::Delete(line.as_span().as_str().to_owned())),
141 Rule::line_inserted => context
142 .data
143 .push(Line::Insert(line.as_span().as_str().to_owned())),
144 _ => {}
145 }
146 }
147 contexts.push_back(context);
148 }
149 _ => {}
150 }
151 }
152
153 let input = input.ok_or_else(|| Error::NotFound("path (input)"))?;
154 let output = output.ok_or_else(|| Error::NotFound("path (output)"))?;
155
156 let patch = Patch {
157 input,
158 output,
159 contexts,
160 };
161
162 Ok(patch)
163 }
164
165 fn get_context_header(header: Pair<'_, Rule>) -> PatchResult<ContextHeader> {
166 let mut output = ContextHeader::default();
167 for header_element in header.into_inner() {
168 match header_element.as_rule() {
169 Rule::file1_l => output.file1_l = header_element.as_span().as_str().parse()?,
170 Rule::file1_s => output.file1_s = header_element.as_span().as_str().parse()?,
171 Rule::file2_l => output.file2_l = header_element.as_span().as_str().parse()?,
172 Rule::file2_s => output.file2_s = header_element.as_span().as_str().parse()?,
173 _ => {}
174 }
175 }
176 Ok(output)
177 }
178}