-
Notifications
You must be signed in to change notification settings - Fork 31
WIP: Patch #344
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
WIP: Patch #344
Changes from all commits
Commits
Show all changes
32 commits
Select commit
Hold shift + click to select a range
959e17b
fixed diff context hunk range identifiers
alirezabeygi803 d473087
WIP patch files
alirezabeygi803 54372a3
updated patch_utils
alirezabeygi803 7f4b3e0
Merge branch 'main' into patch
alirezabeygi803 f0cc8bd
updated patch_utils
alirezabeygi803 59850dc
updated patchutil
alirezabeygi803 18060c4
updated patch
alirezabeygi803 466d4f6
Merge branch 'main' into patch
alirezabeygi803 4568537
introduced patch static files for tests
alirezabeygi803 ed2f340
introduced cli args
alirezabeygi803 1745419
Merge branch 'main' into patch
alirezabeygi803 82cc209
code cleanup
alirezabeygi803 cc40934
Merge branch 'main' into patch
alirezabeygi803 17ddd5f
using regex instead of if-else
alirezabeygi803 c78a5b9
updated patch_util
alirezabeygi803 410d7a1
patch final steps
alirezabeygi803 d602239
fixed build error
alirezabeygi803 09ae4da
Merge branch 'main' into patch
alirezabeygi803 af66c7d
Merge branch 'main' into patch
alirezabeygi803 8622710
updated Cargo.toml
alirezabeygi803 fdcced3
fixed Diff Context Unified Date issue
alirezabeygi803 6ea9675
Merge branch 'main' into patch
alirezabeygi803 1a43944
updated- in progress
alirezabeygi803 7981a75
Merge branch 'main' into patch
alirezabeygi803 8939a3c
Merge branch 'main' into patch
alirezabeygi803 0eb1ddc
Merge branch 'main' into patch
alirezabeygi803 594bf93
Merge remote-tracking branch 'origin/main' into patch
alirezabeygi803 f30310b
ran cargo fmt --all
alirezabeygi803 320f105
Merge branch 'patch' of https://github.com/alirezabeygi803/posixutils…
alirezabeygi803 d51eb5d
supporting both DateTime formats
alirezabeygi803 0cba9d1
Cargo.lock updated
alirezabeygi803 c058d42
Fixed issues
alirezabeygi803 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -107,3 +107,7 @@ path = "./tsort.rs" | |
| [[bin]] | ||
| name = "wc" | ||
| path = "./wc.rs" | ||
|
|
||
| [[bin]] | ||
| name = "patch" | ||
| path = "./patch.rs" | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,210 @@ | ||
| // TODO: Handle no new lines | ||
| // TODO: detects loops! | ||
| // TODO: handle new ed PatchLine variants | ||
| // TODO: Check if patch is in ED or NORMAL format, if yes, output file should be Some(path) | ||
| // TODO: Determine patch kind and read to end of patch, do this again until all patches are extracted and pushed to patch list | ||
| // TODO: if number fo patches is more than 1, then they should be in either Context copied format or unified format, otherwise it is not possible to handle it | ||
| // TODO: If patch file is in context or unified format and file/output-file is not Some(path), extract path of original and modified files, and extract modification-date, | ||
| // TODO: Apply patch and if there is modification-date, apply it to the destination file. | ||
| // TODO: TaDa, done! | ||
| // TODO: Write tests! | ||
| ////////////////////////////////////////////////////////////////////////// | ||
| // TODO : if hunks validation failed, write reject files. | ||
| // TODO : update modified_data of new file according to the patch | ||
| mod patch_utils; | ||
|
|
||
| extern crate clap; | ||
|
|
||
| use std::{ | ||
| io::{self}, | ||
| path::PathBuf, | ||
| }; | ||
|
|
||
| use clap::Parser; | ||
| use patch_utils::{ | ||
| constants::init, | ||
| functions::{context_unified_date_convert, file_exists, if_else, print_error}, | ||
| hunks::Apply, | ||
| patch_file::PatchFile, | ||
| patch_file_kind::FileKind, | ||
| patch_format::PatchFormat, | ||
| patch_options::PatchOptions, | ||
| patch_units::PatchUnits, | ||
| }; | ||
|
|
||
| /// patch - convert original file to modified file and vice versa using result of diff | ||
| #[derive(Parser, Debug, Clone)] | ||
| #[command(author, version, about, long_about)] | ||
| struct Args { | ||
| /// backup original content of each file | ||
| #[arg(short = 'b', long = "backup")] | ||
| backup: bool, | ||
|
|
||
| #[clap(flatten)] | ||
| format: FormatGroup, | ||
|
|
||
| /// assume patches were created with old file and new file swapped | ||
| #[arg(short = 'R', long = "reverse")] | ||
| reverse: bool, | ||
|
|
||
| /// change the working directory to dir first | ||
| #[arg(short = 'd', long = "dir")] | ||
| dir: Option<PathBuf>, | ||
|
|
||
| /// make merged if-then-else output using NAME | ||
| #[arg(short = 'D', long = "ifdef")] | ||
| ifdef: Option<String>, | ||
|
|
||
| /// strip NUM leading components from file names. | ||
| #[arg(short = 'p', long = "strip")] | ||
| num: Option<usize>, | ||
|
|
||
| /// output rejects to REJECT_FILE | ||
| #[arg(short = 'r', long = "reject-file")] | ||
| reject_file: Option<PathBuf>, | ||
|
|
||
| /// output patched files to OUTPUT_FILE | ||
| #[arg(short = 'o', long = "output")] | ||
| output_file: Option<PathBuf>, | ||
|
|
||
| /// file to apply patch to, empty for copied context and unified formats | ||
| #[arg(short, long)] | ||
| file: Option<PathBuf>, | ||
|
|
||
| /// read file from PATCHFILE instead of stdin | ||
| #[arg(short = 'i', long = "input")] | ||
| patchfile: PathBuf, | ||
| } | ||
|
|
||
| #[derive(Debug, Clone, clap::Args)] | ||
| #[group(required = false, multiple = false)] | ||
| pub struct FormatGroup { | ||
| /// interpret the patch file as a copied context difference | ||
| #[clap(short, long)] | ||
| context: bool, | ||
| /// interpret the patch file as an ed script | ||
| #[clap(short, long)] | ||
| ed: bool, | ||
| /// interpret the patch file as a normal script | ||
| #[clap(short, long)] | ||
| normal: bool, | ||
| /// interpret the patch file as a unified script | ||
| #[clap(short, long)] | ||
| unified: bool, | ||
| } | ||
|
|
||
| impl From<Args> for PatchOptions { | ||
| fn from(value: Args) -> Self { | ||
| let patch_format_options = [ | ||
| (value.format.ed, PatchFormat::EditScript), | ||
| (value.format.normal, PatchFormat::Normal), | ||
| (value.format.context, PatchFormat::Context), | ||
| (value.format.unified, PatchFormat::Unified), | ||
| (true, PatchFormat::None), // to ensure next unwrap works | ||
| ]; | ||
|
|
||
| let patch_format = patch_format_options | ||
| .iter() | ||
| .filter(|option| option.0) | ||
| .map(|option| option.1) | ||
| .nth(0) | ||
| .unwrap(); | ||
|
|
||
| PatchOptions { | ||
| backup: value.backup, | ||
| reverse: value.reverse, | ||
| file: value.file, | ||
| output_file: value.output_file, | ||
| strip: value.num, | ||
| patch_format, | ||
| patch_file: value.patchfile, | ||
| reject_file: value.reject_file, | ||
| } | ||
| } | ||
| } | ||
|
|
||
| impl Args { | ||
| fn verify_patchfile_exists(self) -> io::Result<Self> { | ||
| let if_true: fn(&PathBuf) = |_| {}; | ||
|
|
||
| let if_false: fn(&PathBuf) = |file_name: &PathBuf| { | ||
| print_error(format!( | ||
| " **** Can't open patch file {} : No such file or directory", | ||
| file_name.to_str().expect("Could not unwrap file_name") | ||
| )); | ||
| }; | ||
|
|
||
| let file_exists = file_exists(&self.patchfile); | ||
|
|
||
| if_else(file_exists, if_true, if_false)(&self.patchfile); | ||
|
|
||
| match file_exists { | ||
| true => Ok(self), | ||
| false => Err(io::Error::new( | ||
| io::ErrorKind::NotFound, | ||
| "Could not find patch file!", | ||
| )), | ||
| } | ||
| } | ||
|
|
||
| fn change_dir(self) -> io::Result<Self> { | ||
| if let Some(path) = &self.dir { | ||
| std::env::set_current_dir(path)?; | ||
| } | ||
|
|
||
| Ok(self) | ||
| } | ||
|
|
||
| fn try_verify_format_and_output_file(self) -> io::Result<Self> { | ||
| if self.output_file.is_none() && self.file.is_none() { | ||
| if self.format.ed || self.format.normal { | ||
| return Err(io::Error::new( | ||
| io::ErrorKind::InvalidData, | ||
| "patch: applying patch for ed and normal format without specifying output-file is not possible!")); | ||
| } | ||
| } | ||
|
|
||
| Ok(self) | ||
| } | ||
| } | ||
|
|
||
| fn main() { | ||
| patch_operation().expect("Failed!"); | ||
| } | ||
|
|
||
| fn patch_operation() -> io::Result<()> { | ||
| init(); | ||
|
|
||
| let args = Args::parse(); | ||
|
|
||
| let args = args | ||
| .change_dir()? | ||
| .verify_patchfile_exists()? | ||
| .try_verify_format_and_output_file()?; | ||
|
|
||
| let patch_options: PatchOptions = args.into(); | ||
| let patch_file = | ||
| PatchFile::load_file(PathBuf::from(&patch_options.patch_file), FileKind::Patch)?; | ||
|
|
||
| let patch = PatchUnits::try_build(&patch_options, &patch_file)?; | ||
|
|
||
| if let Some(patch_units) = patch { | ||
| let into_hunks = patch_units.into_hunks(); | ||
|
|
||
| match into_hunks { | ||
| Ok(mut into_hunks) => { | ||
| for hunks in into_hunks.hunks.iter_mut() { | ||
| let result = hunks.apply(); | ||
| result.expect("Failed to apply hunk!"); | ||
| } | ||
|
|
||
| for _failures in into_hunks.failures.iter_mut() { | ||
| // let reject_path = | ||
| } | ||
| } | ||
| Err(_) => todo!(), | ||
| } | ||
| } | ||
|
|
||
| Ok(()) | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,131 @@ | ||
| use regex::Regex; | ||
| use std::{collections::HashMap, sync::Once}; | ||
|
|
||
| use crate::patch_utils::patch_unit::PatchUnitKind; | ||
|
|
||
| pub const ORIGINAL_SKIP: usize = 2; // 0 is Hunk separator; 1 is original range | ||
|
|
||
| // CONTEXT REGEX STRINGS | ||
| pub const CONTEXT_FORMAT_HUNK_SEPARATOR_REGEX: &str = r"^\s*\*{15}\s*$"; | ||
| pub const CONTEXT_FORMAT_ORIGINAL_RANGE_REGEX: &str = r"^\s*^\*\*\*\s+(\d+|\d+,\d+)\s+\*{4}\s*$"; | ||
| pub const CONTEXT_FORMAT_MODIFIED_RANGE_REGEX: &str = r"^\s*^-{3}\s+(\d+|\d+,\d+)\s+-{4}\s*$"; | ||
| pub const CONTEXT_FORMAT_FIRST_LINE_REGEX: &str = r"^\s*\*{3}\s*.+\s+(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{9} [+-]\d{4}|\w{3} \w{3} \d{1,2} \d{2}:\d{2}:\d{2} \d{4})?\s*$"; | ||
| pub const CONTEXT_FORMAT_SECOND_LINE_REGEX: &str = r"^\s*-{3}\s*.+\s+(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{9} [+-]\d{4}|\w{3} \w{3} \d{1,2} \d{2}:\d{2}:\d{2} \d{4})?\s*$"; | ||
| pub const CONTEXT_FORMAT_INSERTED_LINE_REGEX: &str = r"^\s*\+\s.*\s*$"; | ||
| pub const CONTEXT_FORMAT_DELETED_LINE_REGEX: &str = r"^\s*-\s.*\s*$"; | ||
| pub const CONTEXT_FORMAT_CHANGED_LINE_REGEX: &str = r"^\s*\!\s.*\s*$"; | ||
| pub const CONTEXT_FORMAT_UNCHANGED_LINE_REGEX: &str = r"^\s*\s\s.*\s*$"; | ||
|
|
||
| const INITIALIZE_CONTEXT_REGEX_CACHE_ONCE: Once = Once::new(); | ||
| static mut CONTEXT_REGEX_CACHE: Option<HashMap<ContextRegexKind, Regex>> = None; | ||
|
|
||
| const ORDERED_KINDS: &[ContextRegexKind] = &[ | ||
| ContextRegexKind::FirstLine, | ||
| ContextRegexKind::SecondLine, | ||
| ContextRegexKind::HunkSeparator, | ||
| ContextRegexKind::OriginalRange, | ||
| ContextRegexKind::ModifiedRange, | ||
| ContextRegexKind::InsertedLine, | ||
| ContextRegexKind::DeletedLine, | ||
| ContextRegexKind::ChangedLine, | ||
| ContextRegexKind::UnchangedLine, | ||
| ]; | ||
|
|
||
| pub fn context_match(line: &str) -> PatchUnitKind { | ||
| let cache = context_regex_cache(); | ||
|
|
||
| for kind in ORDERED_KINDS { | ||
| if cache[kind].is_match(line) { | ||
| return PatchUnitKind::Context(*kind); | ||
| } | ||
| } | ||
|
|
||
| // TODO: NewLine, | ||
| // TODO: NoNewLine | ||
|
|
||
| PatchUnitKind::Unkonw | ||
| } | ||
|
|
||
| #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] | ||
| pub enum ContextRegexKind { | ||
| HunkSeparator, | ||
| OriginalRange, | ||
| ModifiedRange, | ||
| FirstLine, | ||
| SecondLine, | ||
| InsertedLine, | ||
| DeletedLine, | ||
| ChangedLine, | ||
| UnchangedLine, | ||
| } | ||
|
|
||
| pub fn initialize_context_regex_cache() { | ||
| INITIALIZE_CONTEXT_REGEX_CACHE_ONCE.call_once(|| unsafe { | ||
| let mut regex_cache = HashMap::new(); | ||
|
|
||
| regex_cache.insert( | ||
| ContextRegexKind::HunkSeparator, | ||
| Regex::new(CONTEXT_FORMAT_HUNK_SEPARATOR_REGEX) | ||
| .expect("CONTEXT_FORMAT_HUNK_SEPARATOR_REGEX regex is not correct!"), | ||
| ); | ||
|
|
||
| regex_cache.insert( | ||
| ContextRegexKind::OriginalRange, | ||
| Regex::new(CONTEXT_FORMAT_ORIGINAL_RANGE_REGEX) | ||
| .expect("CONTEXT_FORMAT_ORIGINAL_RANGE_REGEX regex is not correct!"), | ||
| ); | ||
|
|
||
| regex_cache.insert( | ||
| ContextRegexKind::ModifiedRange, | ||
| Regex::new(CONTEXT_FORMAT_MODIFIED_RANGE_REGEX) | ||
| .expect("CONTEXT_FORMAT_MODIFIED_RANGE_REGEX regex is not correct!"), | ||
| ); | ||
|
|
||
| regex_cache.insert( | ||
| ContextRegexKind::FirstLine, | ||
| Regex::new(CONTEXT_FORMAT_FIRST_LINE_REGEX) | ||
| .expect("CONTEXT_FORMAT_FIRST_LINE_REGEX regex is not correct!"), | ||
| ); | ||
|
|
||
| regex_cache.insert( | ||
| ContextRegexKind::SecondLine, | ||
| Regex::new(CONTEXT_FORMAT_SECOND_LINE_REGEX) | ||
| .expect("CONTEXT_FORMAT_SECOND_LINE_REGEX regex is not correct!"), | ||
| ); | ||
|
|
||
| regex_cache.insert( | ||
| ContextRegexKind::InsertedLine, | ||
| Regex::new(CONTEXT_FORMAT_INSERTED_LINE_REGEX) | ||
| .expect("CONTEXT_FORMAT_INSERTED_LINE_REGEX regex is not correct!"), | ||
| ); | ||
|
|
||
| regex_cache.insert( | ||
| ContextRegexKind::DeletedLine, | ||
| Regex::new(CONTEXT_FORMAT_DELETED_LINE_REGEX) | ||
| .expect("CONTEXT_FORMAT_DELETED_LINE_REGEX regex is not correct!"), | ||
| ); | ||
|
|
||
| regex_cache.insert( | ||
| ContextRegexKind::ChangedLine, | ||
| Regex::new(CONTEXT_FORMAT_CHANGED_LINE_REGEX) | ||
| .expect("CONTEXT_FORMAT_CHANGED_LINE_REGEX regex is not correct!"), | ||
| ); | ||
|
|
||
| regex_cache.insert( | ||
| ContextRegexKind::UnchangedLine, | ||
| Regex::new(CONTEXT_FORMAT_UNCHANGED_LINE_REGEX) | ||
| .expect("CONTEXT_FORMAT_UNCHANGED_LINE_REGEX regex is not correct!"), | ||
| ); | ||
|
|
||
| CONTEXT_REGEX_CACHE = Some(regex_cache); | ||
| }); | ||
| } | ||
|
|
||
| pub fn context_regex_cache() -> &'static HashMap<ContextRegexKind, Regex> { | ||
| #[allow(static_mut_refs)] | ||
| if let Some(original_normal_regex_cache) = unsafe { &CONTEXT_REGEX_CACHE } { | ||
| return original_normal_regex_cache; | ||
| } | ||
|
|
||
| panic!("NORMAL_REGEX_CACHE should not be empty!"); | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Update the panic message to reference 'CONTEXT_REGEX_CACHE' instead of 'NORMAL_REGEX_CACHE' for clarity.