1use difference_rs::Changeset;
2use serde::{Deserialize, Serialize};
3use std::fs::{self, File};
4use std::io::Read;
5use std::path::{Path, PathBuf};
6use walkdir::WalkDir;
7
8use crate::regex::RegexIR;
9
10#[cfg(feature = "lua")]
11use crate::lua_exec;
12#[cfg(feature = "rhai")]
13use crate::rhai_exec;
14use crate::sh_exec;
15
16#[derive(Deserialize, Serialize, PartialEq, Default, Clone)]
17pub struct PatchFile {
21 pub patches: Vec<Patch>,
23}
24
25#[derive(Deserialize, Serialize, PartialEq, Default, Clone)]
26pub struct Patch {
28 pub files: Vec<FilePath>,
30 #[serde(skip_serializing_if = "Option::is_none")]
31 pub decoder: Option<DecodeBy>,
35 #[serde(skip_serializing_if = "Option::is_none")]
36 pub encoder: Option<EncodeBy>,
40 pub patch_area: Vec<AreaRule>,
44 #[serde(skip_serializing_if = "Option::is_none")]
45 pub replace: Option<Replacer>,
47 #[serde(skip_serializing_if = "Option::is_none")]
48 pub insert: Option<String>,
50}
51
52#[derive(Deserialize, Serialize, PartialEq, Clone)]
53#[serde(rename_all = "snake_case")]
54pub enum AreaRule {
56 Contains(RegexIR),
58 NotContains(RegexIR),
60 Before(RegexIR),
62 After(RegexIR),
64 CursorAtBegin,
66 CursorAtEnd,
68 FindByLua(PathBuf),
70 FindByRhai(PathBuf),
72 FindBySh(PathBuf),
74}
75
76#[derive(Deserialize, Serialize, PartialEq, Clone)]
78#[serde(rename_all = "snake_case")]
79pub enum DecodeBy {
80 Lua(PathBuf),
82 Rhai(PathBuf),
84 Sh(PathBuf),
86}
87
88#[derive(Deserialize, Serialize, PartialEq, Clone)]
90#[serde(rename_all = "snake_case")]
91pub enum EncodeBy {
92 Lua(PathBuf),
94 Rhai(PathBuf),
96 Sh(PathBuf),
98}
99
100#[derive(Deserialize, Serialize, PartialEq, Clone)]
102#[serde(rename_all = "snake_case")]
103pub enum Replacer {
104 FromTo(String, String),
106 RegexTo(RegexIR, String),
110 ByLua(PathBuf),
112 ByRhai(PathBuf),
114 BySh(PathBuf),
116}
117
118#[derive(Deserialize, Serialize, PartialEq, Clone)]
119#[serde(rename_all = "snake_case")]
120pub enum FilePath {
122 Just(PathBuf),
124 Re(RegexIR),
128}
129
130enum CursorType {
131 Start,
132 End,
133}
134
135impl PatchFile {
136 pub fn patch(&self, root: &Path, patch_dir: &Path) -> anyhow::Result<usize> {
138 let mut cntr = 0;
139
140 if root.is_dir() {
141 for entry in WalkDir::new(root).into_iter().filter_map(|e| e.ok()) {
142 let path = entry.path();
143 if !path.is_file() {
144 continue;
145 }
146 for patch in &self.patches {
147 if self.should_process_file(path, &patch.files) {
148 let content = PatchFile::read(path, patch_dir, &patch.decoder)?;
149 if let Some(res) = PatchFile::apply_patch(content, patch_dir, patch, &mut cntr)? {
150 PatchFile::write(path, patch_dir, &patch.encoder, &res)?;
151 }
152 }
153 }
154 }
155 } else {
156 for patch in &self.patches {
157 if self.should_process_file(root, &patch.files) {
158 let content = PatchFile::read(root, patch_dir, &patch.decoder)?;
159 if let Some(res) = PatchFile::apply_patch(content, patch_dir, patch, &mut cntr)? {
160 PatchFile::write(root, patch_dir, &patch.encoder, &res)?;
161 }
162 }
163 }
164 }
165
166 Ok(cntr)
167 }
168
169 pub fn test(&self, root: &Path, patch_dir: &Path) -> anyhow::Result<usize> {
171 let mut cntr = 0;
172
173 if root.is_dir() {
174 for entry in WalkDir::new(root).into_iter().filter_map(|e| e.ok()) {
175 let path = entry.path();
176 if !path.is_file() {
177 continue;
178 }
179 for patch in &self.patches {
180 if self.should_process_file(path, &patch.files) {
181 let content = PatchFile::read(path, patch_dir, &patch.decoder)?;
182 if let Some(res) = PatchFile::apply_patch(content.clone(), patch_dir, patch, &mut cntr)? {
183 let diffs = Changeset::new(&content, &res, "");
184 println!("In file {path:?}:");
185 println!("{diffs}");
186 println!();
187 }
188 }
189 }
190 }
191 } else {
192 let path = root;
193 for patch in &self.patches {
194 if self.should_process_file(path, &patch.files) {
195 let content = PatchFile::read(path, patch_dir, &patch.decoder)?;
196 if let Some(res) = PatchFile::apply_patch(content.clone(), patch_dir, patch, &mut cntr)? {
197 let diffs = Changeset::new(&content, &res, "");
198 println!("In file {path:?}:");
199 println!("{diffs}");
200 println!();
201 }
202 }
203 }
204 }
205
206 Ok(cntr)
207 }
208
209 fn should_process_file(&self, path: &Path, file_patterns: &[FilePath]) -> bool {
210 if file_patterns.is_empty() {
211 return true;
212 }
213
214 let path_str = path.to_string_lossy();
215
216 file_patterns.iter().any(|pattern| match pattern {
217 FilePath::Just(exact_path) => path.ends_with(exact_path),
218 FilePath::Re(regex) => regex.inner().is_match(&path_str),
219 })
220 }
221
222 fn read(path: &Path, _patch_dir: &Path, decoder: &Option<DecodeBy>) -> anyhow::Result<String> {
223 #[allow(unreachable_patterns)]
224 match decoder {
225 None => {
226 let mut content = String::new();
227 File::open(path)?.read_to_string(&mut content)?;
228 Ok(content)
229 }
230 Some(DecodeBy::Sh(script)) => Ok(sh_exec::decode_by(_patch_dir, script, path.canonicalize()?)?),
231 #[cfg(feature = "lua")]
232 Some(DecodeBy::Lua(script)) => {
233 let mut content = vec![];
234 File::open(path)?.read_to_end(&mut content)?;
235 Ok(lua_exec::decode_by(_patch_dir, script, &content)?)
236 }
237 #[cfg(feature = "rhai")]
238 Some(DecodeBy::Rhai(script)) => {
239 let mut content = vec![];
240 File::open(path)?.read_to_end(&mut content)?;
241 Ok(rhai_exec::decode_by(_patch_dir, script, &content)?)
242 }
243 Some(_) => {
244 anyhow::bail!("Given decoder variant is unsupported. Build `smart-patcher` with `lua` or `rhai` feature.")
245 }
246 }
247 }
248
249 fn write(path: &Path, _patch_dir: &Path, encoder: &Option<EncodeBy>, content: &str) -> anyhow::Result<()> {
250 #[allow(unreachable_patterns)]
251 match encoder {
252 None => {
253 fs::write(path, content)?;
254 Ok(())
255 }
256 Some(EncodeBy::Sh(script)) => {
257 sh_exec::encode_by(_patch_dir, script, content, path.canonicalize()?)?;
258 Ok(())
259 }
260 #[cfg(feature = "lua")]
261 Some(EncodeBy::Lua(script)) => {
262 let content = lua_exec::encode_by(_patch_dir, script, content)?;
263 fs::write(path, content)?;
264 Ok(())
265 }
266 #[cfg(feature = "rhai")]
267 Some(EncodeBy::Rhai(script)) => {
268 let content = rhai_exec::encode_by(_patch_dir, script, content)?;
269 fs::write(path, content)?;
270 Ok(())
271 }
272 Some(_) => {
273 anyhow::bail!("Given encoder variant is unsupported. Build `smart-patcher` with `lua` or `rhai` feature.")
274 }
275 }
276 }
277
278 fn apply_patch(content: String, patch_dir: &Path, patch: &Patch, cntr: &mut usize) -> anyhow::Result<Option<String>> {
279 if let Some((range, cursor)) = PatchFile::find_section(&content, patch_dir, &patch.patch_area)? {
280 let before = &content[..range.start];
281 let after = &content[range.end..];
282
283 let mut middle = content[range.start..range.end].to_owned();
284 if !middle.is_empty()
285 && let Some(replacer) = &patch.replace
286 {
287 #[allow(unreachable_patterns)]
288 match replacer {
289 Replacer::FromTo(from, to) => {
290 middle = middle.replace(from, to);
291 }
292 Replacer::RegexTo(from_re, to) => {
293 middle = from_re.replace_all(&middle, to.as_str()).to_string();
294 }
295 Replacer::BySh(path) => {
296 middle = sh_exec::replace_by(patch_dir, path, &middle)?;
297 }
298 #[cfg(feature = "lua")]
299 Replacer::ByLua(path) => {
300 middle = lua_exec::replace_by(patch_dir, path, &middle)?;
301 }
302 #[cfg(feature = "rhai")]
303 Replacer::ByRhai(path) => {
304 middle = rhai_exec::replace_by(patch_dir, path, &middle)?;
305 }
306 _ => {
307 anyhow::bail!("Given replacer variant is unsupported. Build `smart-patcher` with `lua` or `rhai` feature.")
308 }
309 }
310 }
311 if let Some(insert) = &patch.insert {
312 if middle.is_empty() {
313 middle = insert.to_owned();
314 } else {
315 middle = match cursor {
316 CursorType::Start => insert.to_owned() + middle.as_str(),
317 CursorType::End => middle + insert.as_str(),
318 }
319 }
320 }
321
322 let res = format!("{before}{middle}{after}");
323
324 #[cfg(test)]
325 {
326 println!("{}", content);
327 println!("{}", res);
328 }
329
330 if !res.as_str().eq(content.as_str()) {
331 *cntr += 1;
332 }
333 return Ok(Some(res));
334 }
335
336 Ok(None)
337 }
338
339 fn find_section(
340 content: &str,
341 _patch_dir: &Path,
342 rules: &[AreaRule],
343 ) -> anyhow::Result<Option<(std::ops::Range<usize>, CursorType)>> {
344 let mut current_pos = 0;
345 let mut start_pos = None;
346 let mut end_pos = None;
347 let mut cursor = CursorType::Start;
348
349 for rule in rules {
350 #[allow(unreachable_patterns)]
351 match &rule {
352 AreaRule::Contains(regex) => {
353 if !regex.inner().is_match(&content[current_pos..]) {
354 return Ok(None);
355 }
356 }
357 AreaRule::NotContains(regex) => {
358 if regex.inner().is_match(&content[current_pos..]) {
359 return Ok(None);
360 }
361 }
362 AreaRule::Before(regex) => {
363 cursor = CursorType::End;
364
365 if let Some(mat) = regex.inner().find(&content[current_pos..]) {
366 end_pos = Some(current_pos + mat.start());
367 } else {
368 return Ok(None);
369 }
370 }
371 AreaRule::After(regex) => {
372 cursor = CursorType::Start;
373
374 if let Some(mat) = regex.inner().find(&content[current_pos..]) {
375 start_pos = Some(current_pos + mat.end());
376 current_pos += mat.end();
377 } else {
378 return Ok(None);
379 }
380 }
381 AreaRule::CursorAtBegin => {
382 cursor = CursorType::Start;
383 }
384 AreaRule::CursorAtEnd => {
385 cursor = CursorType::End;
386 }
387 AreaRule::FindBySh(path) => {
388 let (new_start, new_end, cursor_at_end) = sh_exec::find_by(_patch_dir, path, content, start_pos, end_pos)?;
389
390 if new_start.is_some() {
391 start_pos = new_start;
392 }
393 if new_end.is_some() {
394 end_pos = new_end;
395 }
396
397 if cursor_at_end {
398 cursor = CursorType::End;
399 } else {
400 cursor = CursorType::Start;
401 }
402 }
403 #[cfg(feature = "lua")]
404 AreaRule::FindByLua(path) => {
405 let (new_start, new_end, cursor_at_end) = lua_exec::find_by(_patch_dir, path, content, start_pos, end_pos)?;
406
407 if start_pos.is_none() && new_start != 0 {
408 start_pos = Some(new_start);
409 }
410 if end_pos.is_none() && new_end != content.len() {
411 end_pos = Some(new_end);
412 }
413
414 if cursor_at_end {
415 cursor = CursorType::End;
416 } else {
417 cursor = CursorType::Start;
418 }
419 }
420 #[cfg(feature = "rhai")]
421 AreaRule::FindByRhai(path) => {
422 let (new_start, new_end, cursor_at_end) = rhai_exec::find_by(_patch_dir, path, content, start_pos, end_pos)?;
423
424 if start_pos.is_none() && new_start != 0 {
425 start_pos = Some(new_start);
426 }
427 if end_pos.is_none() && new_end != content.len() {
428 end_pos = Some(new_end);
429 }
430
431 if cursor_at_end {
432 cursor = CursorType::End;
433 } else {
434 cursor = CursorType::Start;
435 }
436 }
437 _ => {
438 anyhow::bail!("Given find rule variant is unsupported. Build `smart-patcher` with `lua` or `rhai` feature.")
439 }
440 }
441 }
442
443 let range = match (start_pos, end_pos) {
444 (Some(start), Some(end)) if start <= end => Some(start..end),
445 (Some(start), None) => Some(start..content.len()),
446 (None, Some(end)) => Some(0..end),
447 _ => Some(0..content.len()),
448 };
449
450 if let Some(range) = range {
451 Ok(Some((range, cursor)))
452 } else {
453 Ok(None)
454 }
455 }
456}
457
458#[cfg(test)]
459mod tests {
460 use super::*;
461
462 #[test]
463 fn find_v1() -> anyhow::Result<()> {
464 let patch = Patch {
465 files: vec![],
466 patch_area: vec![AreaRule::After(RegexIR::new("media")?)],
467 replace: Some(Replacer::FromTo(String::from("ttt"), String::from("yyy"))),
468 insert: None,
469 decoder: None,
470 encoder: None,
471 };
472
473 let content = String::from("ttt is a new media: there is only ttt");
474 let mut cntr = 0;
475 let res = PatchFile::apply_patch(content, &PathBuf::from("."), &patch, &mut cntr)?;
476 assert_eq!(res, Some(String::from("ttt is a new media: there is only yyy")));
477 assert_eq!(cntr, 1);
478
479 Ok(())
480 }
481
482 #[test]
483 fn find_v2() -> anyhow::Result<()> {
484 let patch = Patch {
485 files: vec![],
486 patch_area: vec![AreaRule::After(RegexIR::new("media")?)],
487 replace: None,
488 insert: Some(String::from(" v2")),
489 decoder: None,
490 encoder: None,
491 };
492
493 let content = String::from("ttt is a new media: there is only ttt");
494 let mut cntr = 0;
495 let res = PatchFile::apply_patch(content, &PathBuf::from("."), &patch, &mut cntr)?;
496 assert_eq!(res, Some(String::from("ttt is a new media v2: there is only ttt")));
497 assert_eq!(cntr, 1);
498
499 Ok(())
500 }
501
502 #[test]
503 fn find_v3() -> anyhow::Result<()> {
504 let patch = Patch {
505 files: vec![],
506 patch_area: vec![AreaRule::Before(RegexIR::new("media")?)],
507 replace: None,
508 insert: Some(String::from("v2 ")),
509 decoder: None,
510 encoder: None,
511 };
512
513 let content = String::from("ttt is a new media: there is only ttt");
514 let mut cntr = 0;
515 let res = PatchFile::apply_patch(content, &PathBuf::from("."), &patch, &mut cntr)?;
516 assert_eq!(res, Some(String::from("ttt is a new v2 media: there is only ttt")));
517 assert_eq!(cntr, 1);
518
519 Ok(())
520 }
521
522 #[test]
523 fn find_v4() -> anyhow::Result<()> {
524 let patch = Patch {
525 files: vec![],
526 patch_area: vec![
527 AreaRule::Before(RegexIR::new(": there")?),
528 AreaRule::After(RegexIR::new("new ")?),
529 AreaRule::FindBySh(PathBuf::from("tests/test_v4.py")),
530 ],
531 replace: None,
532 insert: Some(String::from(" v2")),
533 decoder: None,
534 encoder: None,
535 };
536
537 let content = String::from("ttt is a new media: there is only ttt");
538 let mut cntr = 0;
539 let res = PatchFile::apply_patch(content, &PathBuf::from("."), &patch, &mut cntr)?;
540 assert_eq!(res, Some(String::from("ttt is a new media v2: there is only ttt")));
541 assert_eq!(cntr, 1);
542
543 Ok(())
544 }
545
546 #[test]
547 fn find_v5() -> anyhow::Result<()> {
548 let patch = Patch {
549 files: vec![FilePath::Just(PathBuf::from("test_v5.docx"))],
550 patch_area: vec![],
551 replace: Some(Replacer::FromTo("game".to_string(), "rock".to_string())),
552 insert: None,
553 decoder: Some(DecodeBy::Sh(PathBuf::from("tests/test_v5.py"))),
554 encoder: Some(EncodeBy::Sh(PathBuf::from("tests/test_v5.py"))),
555 };
556
557 let pf = PatchFile { patches: vec![patch] };
558 let cntr = pf.patch(&PathBuf::from("tests"), &PathBuf::from("."))?;
559
560 assert_eq!(cntr, 1);
561
562 Ok(())
563 }
564
565 #[test]
566 #[cfg(feature = "lua")]
567 fn find_v6() -> anyhow::Result<()> {
568 let patch = Patch {
569 files: vec![],
570 patch_area: vec![
571 AreaRule::Before(RegexIR::new(": there")?),
572 AreaRule::After(RegexIR::new("new ")?),
573 AreaRule::FindByLua(PathBuf::from("tests/test_v6.lua")),
574 ],
575 replace: None,
576 insert: Some(String::from(" v2")),
577 decoder: None,
578 encoder: None,
579 };
580
581 let content = String::from("ttt is a new media: there is only ttt");
582 let mut cntr = 0;
583 let res = PatchFile::apply_patch(content, &PathBuf::from("."), &patch, &mut cntr)?;
584 assert_eq!(res, Some(String::from("ttt is a new media v2: there is only ttt")));
585 assert_eq!(cntr, 1);
586
587 Ok(())
588 }
589
590 #[test]
591 #[cfg(feature = "rhai")]
592 fn find_v7() -> anyhow::Result<()> {
593 let patch = Patch {
594 files: vec![],
595 patch_area: vec![
596 AreaRule::Before(RegexIR::new(": there")?),
597 AreaRule::After(RegexIR::new("new ")?),
598 AreaRule::FindByRhai(PathBuf::from("tests/test_v7.rhai")),
599 ],
600 replace: None,
601 insert: Some(String::from(" v2")),
602 decoder: None,
603 encoder: None,
604 };
605
606 let content = String::from("ttt is a new media: there is only ttt");
607 let mut cntr = 0;
608 let res = PatchFile::apply_patch(content, &PathBuf::from("."), &patch, &mut cntr)?;
609 assert_eq!(res, Some(String::from("ttt is a new media v2: there is only ttt")));
610 assert_eq!(cntr, 1);
611
612 Ok(())
613 }
614
615 #[test]
616 fn find_v8() -> anyhow::Result<()> {
617 let patch = Patch {
618 files: vec![],
619 patch_area: vec![],
620 replace: Some(Replacer::BySh(PathBuf::from("tests/test_v8.py"))),
621 insert: None,
622 decoder: None,
623 encoder: None,
624 };
625
626 let content = String::from("This is my number: +18235123154");
627 let mut cntr = 0;
628 let res = PatchFile::apply_patch(content, &PathBuf::from("."), &patch, &mut cntr)?;
629 assert_eq!(res, Some(String::from("This is my number: +28235223254")));
630 assert_eq!(cntr, 1);
631
632 Ok(())
633 }
634
635 #[test]
636 fn find_v9() -> anyhow::Result<()> {
637 let patch = Patch {
638 files: vec![],
639 patch_area: vec![],
640 replace: Some(Replacer::BySh(PathBuf::from("tests/test_v9.py"))),
641 insert: None,
642 decoder: None,
643 encoder: None,
644 };
645
646 let content = String::from("This is my project name: {1}");
647 let mut cntr = 0;
648 let res = PatchFile::apply_patch(content, &PathBuf::from("."), &patch, &mut cntr)?;
649 assert_eq!(res, Some(String::from("This is my project name: smart-patcher")));
650 assert_eq!(cntr, 1);
651
652 Ok(())
653 }
654
655 #[test]
656 fn find_v10() -> anyhow::Result<()> {
657 let patch = Patch {
658 files: vec![],
659 patch_area: vec![
660 AreaRule::Before(RegexIR::new(": there")?),
661 AreaRule::After(RegexIR::new("new ")?),
662 AreaRule::CursorAtEnd,
663 ],
664 replace: None,
665 insert: Some(String::from(" v2")),
666 decoder: None,
667 encoder: None,
668 };
669
670 let content = String::from("ttt is a new media: there is only ttt");
671 let mut cntr = 0;
672 let res = PatchFile::apply_patch(content, &PathBuf::from("."), &patch, &mut cntr)?;
673 assert_eq!(res, Some(String::from("ttt is a new media v2: there is only ttt")));
674 assert_eq!(cntr, 1);
675
676 Ok(())
677 }
678}
679
680#[cfg(feature = "generate-patches")]
681#[allow(dead_code)]
682pub(crate) fn generate_examples() -> anyhow::Result<()> {
683 use std::io::BufWriter;
684
685 if !PathBuf::from("examples").exists() {
686 fs::create_dir("examples")?;
687 }
688
689 let patch = Patch {
690 files: vec![],
691 patch_area: vec![AreaRule::After(RegexIR::new("media")?)],
692 replace: Some(Replacer::FromTo(String::from("ttt"), String::from("yyy"))),
693 insert: None,
694 decoder: None,
695 encoder: None,
696 };
697 let pf = PatchFile { patches: vec![patch] };
698 let f = File::create("examples/patch1.json")?;
699 let buf = BufWriter::new(f);
700 serde_json::to_writer_pretty(buf, &pf)?;
701
702 let patch = Patch {
703 files: vec![],
704 patch_area: vec![AreaRule::After(RegexIR::new("media")?)],
705 replace: None,
706 insert: Some(String::from(" v2")),
707 decoder: None,
708 encoder: None,
709 };
710 let pf = PatchFile { patches: vec![patch] };
711 let f = File::create("examples/patch2.json")?;
712 let buf = BufWriter::new(f);
713 serde_json::to_writer_pretty(buf, &pf)?;
714
715 let patch = Patch {
716 files: vec![],
717 patch_area: vec![AreaRule::Before(RegexIR::new("media")?)],
718 replace: None,
719 insert: Some(String::from("v2 ")),
720 decoder: None,
721 encoder: None,
722 };
723 let pf = PatchFile { patches: vec![patch] };
724 let f = File::create("examples/patch3.json")?;
725 let buf = BufWriter::new(f);
726 serde_json::to_writer_pretty(buf, &pf)?;
727
728 let patch = Patch {
729 files: vec![],
730 patch_area: vec![
731 AreaRule::Before(RegexIR::new(": there")?),
732 AreaRule::After(RegexIR::new("new ")?),
733 AreaRule::FindBySh(PathBuf::from("../tests/test_v4.py")),
734 ],
735 replace: None,
736 insert: Some(String::from(" v2")),
737 decoder: None,
738 encoder: None,
739 };
740 let pf = PatchFile { patches: vec![patch] };
741 let f = File::create("examples/patch4.json")?;
742 let buf = BufWriter::new(f);
743 serde_json::to_writer_pretty(buf, &pf)?;
744
745 let patch = Patch {
746 files: vec![FilePath::Just(PathBuf::from("test_v5.docx"))],
747 patch_area: vec![],
748 replace: Some(Replacer::FromTo("game".to_string(), "rock".to_string())),
749 insert: None,
750 decoder: Some(DecodeBy::Sh(PathBuf::from("../tests/test_v5.py"))),
751 encoder: Some(EncodeBy::Sh(PathBuf::from("../tests/test_v5.py"))),
752 };
753 let pf = PatchFile { patches: vec![patch] };
754 let f = File::create("examples/patch5.json")?;
755 let buf = BufWriter::new(f);
756 serde_json::to_writer_pretty(buf, &pf)?;
757
758 let patch = Patch {
759 files: vec![],
760 patch_area: vec![
761 AreaRule::Before(RegexIR::new(": there")?),
762 AreaRule::After(RegexIR::new("new ")?),
763 AreaRule::FindByLua(PathBuf::from("../tests/test_v6.lua")),
764 ],
765 replace: None,
766 insert: Some(String::from(" v2")),
767 decoder: None,
768 encoder: None,
769 };
770 let pf = PatchFile { patches: vec![patch] };
771 let f = File::create("examples/patch6.json")?;
772 let buf = BufWriter::new(f);
773 serde_json::to_writer_pretty(buf, &pf)?;
774
775 let patch = Patch {
776 files: vec![],
777 patch_area: vec![
778 AreaRule::Before(RegexIR::new(": there")?),
779 AreaRule::After(RegexIR::new("new ")?),
780 AreaRule::FindByRhai(PathBuf::from("../tests/test_v7.rhai")),
781 ],
782 replace: None,
783 insert: Some(String::from(" v2")),
784 decoder: None,
785 encoder: None,
786 };
787 let pf = PatchFile { patches: vec![patch] };
788 let f = File::create("examples/patch7.json")?;
789 let buf = BufWriter::new(f);
790 serde_json::to_writer_pretty(buf, &pf)?;
791
792 let patch = Patch {
793 files: vec![],
794 patch_area: vec![],
795 replace: Some(Replacer::BySh(PathBuf::from("../tests/test_v8.py"))),
796 insert: None,
797 decoder: None,
798 encoder: None,
799 };
800 let pf = PatchFile { patches: vec![patch] };
801 let f = File::create("examples/patch8.json")?;
802 let buf = BufWriter::new(f);
803 serde_json::to_writer_pretty(buf, &pf)?;
804
805 let patch = Patch {
806 files: vec![],
807 patch_area: vec![],
808 replace: Some(Replacer::BySh(PathBuf::from("../tests/test_v9.py"))),
809 insert: None,
810 decoder: None,
811 encoder: None,
812 };
813 let pf = PatchFile { patches: vec![patch] };
814 let f = File::create("examples/patch9.json")?;
815 let buf = BufWriter::new(f);
816 serde_json::to_writer_pretty(buf, &pf)?;
817
818 let patch = Patch {
819 files: vec![],
820 patch_area: vec![
821 AreaRule::Before(RegexIR::new(": there")?),
822 AreaRule::After(RegexIR::new("new ")?),
823 AreaRule::CursorAtEnd,
824 ],
825 replace: None,
826 insert: Some(String::from(" v2")),
827 decoder: None,
828 encoder: None,
829 };
830 let pf = PatchFile { patches: vec![patch] };
831 let f = File::create("examples/patch10.json")?;
832 let buf = BufWriter::new(f);
833 serde_json::to_writer_pretty(buf, &pf)?;
834
835 Ok(())
836}