patch_rs/
parser.rs

1//!
2//! The Patch parser implementation.
3//!
4
5use 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}