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