diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 1e3a81f..7a52634 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -5,15 +5,33 @@ on: branches: [ main ] pull_request: branches: [ main ] + # allow manual trigger + workflow_dispatch: env: CARGO_TERM_COLOR: always jobs: test: - runs-on: ubuntu-latest + name: Build and test for ${{matrix.rust}} on ${{matrix.os}} + runs-on: ${{matrix.os}} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + rust: + - 1.84.1 # pre edition2024 + - 1.87 + - stable steps: - uses: actions/checkout@v2 + + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: ${{matrix.rust}} + override: true + - name: Build lib run: cargo build --verbose - name: Run tests @@ -22,13 +40,22 @@ jobs: run: cargo build --example '*' style: - runs-on: ubuntu-latest + name: Style checks for ${{matrix.rust}} on ${{matrix.os}} + runs-on: ${{matrix.os}} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + rust: + - 1.84.1 # pre edition2024 + - 1.87 # clippy became more picky after this + - stable steps: - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: stable + toolchain: ${{matrix.rust}} override: true components: rustfmt, clippy - name: fmt diff --git a/src/parser.rs b/src/parser.rs index 8c7b00b..487f3e2 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -68,7 +68,7 @@ fn consume_content_line(input: Input<'_>) -> IResult, &str> { Ok((input, raw.fragment())) } -pub(crate) fn parse_single_patch(s: &str) -> Result> { +pub(crate) fn parse_single_patch(s: &str) -> Result, ParseError<'_>> { let (remaining_input, patch) = patch(Input::new(s))?; // Parser should return an error instead of producing remaining input assert!( @@ -80,7 +80,7 @@ pub(crate) fn parse_single_patch(s: &str) -> Result> { Ok(patch) } -pub(crate) fn parse_multiple_patches(s: &str) -> Result, ParseError<'_>> { +pub(crate) fn parse_multiple_patches(s: &str) -> Result>, ParseError<'_>> { let (remaining_input, patches) = multiple_patches(Input::new(s))?; // Parser should return an error instead of producing remaining input if !remaining_input.fragment().is_empty() { @@ -94,11 +94,11 @@ pub(crate) fn parse_multiple_patches(s: &str) -> Result, ParseError<' Ok(patches) } -fn multiple_patches(input: Input<'_>) -> IResult, Vec> { +fn multiple_patches(input: Input) -> IResult> { many1(patch)(input) } -fn patch(input: Input<'_>) -> IResult, Patch> { +fn patch(input: Input) -> IResult { if let Ok(patch) = binary_files_differ(input) { return Ok(patch); } @@ -124,7 +124,7 @@ fn patch(input: Input<'_>) -> IResult, Patch> { } /// Recognize a "binary files XX and YY differ" line as an empty patch. -fn binary_files_differ(input: Input<'_>) -> IResult, Patch> { +fn binary_files_differ(input: Input) -> IResult { // The names aren't quoted so this seems to require lookahead and then // parsing the identified string. let (input, (old, new)) = context( @@ -162,7 +162,7 @@ fn binary_files_differ(input: Input<'_>) -> IResult, Patch> { /// /// The `parse` function should handle rename diffs with similary index less than 100%, at least as per the test /// `parses_file_renames_with_some_diff`. -fn file_rename_only(input: Input<'_>) -> IResult, Patch> { +fn file_rename_only(input: Input<'_>) -> IResult, Patch<'_>> { let (rest, _parsed) = take_until("\nsimilarity index 100%\n")(input)?; let (rest, _parsed) = tag("\nsimilarity index 100%\n")(rest)?; @@ -188,7 +188,7 @@ fn file_rename_only(input: Input<'_>) -> IResult, Patch> { } // Header lines -fn headers(input: Input<'_>) -> IResult, (File, File)> { +fn headers(input: Input) -> IResult { // Ignore any preamble lines in produced diffs let (input, _) = take_until("--- ")(input)?; let (input, _) = tag("--- ")(input)?; @@ -200,7 +200,7 @@ fn headers(input: Input<'_>) -> IResult, (File, File)> { Ok((input, (oldfile, newfile))) } -fn header_line_content(input: Input<'_>) -> IResult, File> { +fn header_line_content(input: Input) -> IResult { let (input, filename) = filename(input)?; let (input, after) = opt(preceded(char('\t'), file_metadata))(input)?; @@ -223,7 +223,7 @@ fn header_line_content(input: Input<'_>) -> IResult, File> { } // Hunks of the file differences -fn chunks(input: Input<'_>) -> IResult, Vec> { +fn chunks(input: Input) -> IResult> { many1(chunk)(input) } @@ -235,7 +235,6 @@ fn is_next_header(input: Input<'_>) -> bool { || input.starts_with("@@ ") } - /// Looks for lines starting with + or - or space, but not +++ or ---. Not a foolproof check. /// /// For example, if someone deletes a line that was using the pre-decrement (--) operator or adds a @@ -264,7 +263,7 @@ fn is_next_header(input: Input<'_>) -> bool { ///FIXME: Use the ranges in the chunk header to figure out how many chunk lines to parse. Will need /// to figure out how to count in nom more robustly than many1!(). Maybe using switch!()? ///FIXME: The test_parse_triple_plus_minus_hack test will no longer panic when this is fixed. -fn chunk(input: Input<'_>) -> IResult, Hunk> { +fn chunk(input: Input) -> IResult { let (input, ranges) = chunk_header(input)?; // Parse chunk lines, using the range information to guide parsing @@ -338,11 +337,11 @@ fn no_newline_indicator(input: Input<'_>) -> IResult, bool> { )(input) } -fn filename(input: Input<'_>) -> IResult, Cow> { +fn filename(input: Input) -> IResult> { alt((quoted, bare))(input) } -fn file_metadata(input: Input<'_>) -> IResult, Cow> { +fn file_metadata(input: Input) -> IResult> { alt(( quoted, map(not_line_ending, |data: Input<'_>| { @@ -351,28 +350,28 @@ fn file_metadata(input: Input<'_>) -> IResult, Cow> { ))(input) } -fn quoted(input: Input<'_>) -> IResult, Cow> { +fn quoted(input: Input) -> IResult> { delimited(char('\"'), unescaped_str, char('\"'))(input) } -fn bare(input: Input<'_>) -> IResult, Cow> { +fn bare(input: Input) -> IResult> { map(is_not("\t\r\n"), |data: Input<'_>| { Cow::Borrowed(*data.fragment()) })(input) } -fn unescaped_str(input: Input<'_>) -> IResult, Cow> { +fn unescaped_str(input: Input) -> IResult> { let (input, raw) = many1(alt((unescaped_char, escaped_char)))(input)?; Ok((input, raw.into_iter().collect::>())) } // Parses an unescaped character -fn unescaped_char(input: Input<'_>) -> IResult, char> { +fn unescaped_char(input: Input) -> IResult { none_of("\0\n\r\t\\\"")(input) } // Parses an escaped character and returns its unescaped equivalent -fn escaped_char(input: Input<'_>) -> IResult, char> { +fn escaped_char(input: Input) -> IResult { map(preceded(char('\\'), one_of(r#"0nrt"\"#)), |ch| match ch { '0' => '\0', 'n' => '\n', diff --git a/tests/parse_samples.rs b/tests/parse_samples.rs index aac580d..50e223a 100644 --- a/tests/parse_samples.rs +++ b/tests/parse_samples.rs @@ -20,6 +20,7 @@ fn parse_samples() { // Make sure that the patch file we produce parses to the same information as the original // patch file. + #[allow(clippy::format_collect)] // Display::fmt is the only way to resolve Patch->str let patch_file: String = patches.iter().map(|patch| format!("{}\n", patch)).collect(); println!("{}", patch_file); let patches2 = Patch::from_multiple(&patch_file).unwrap_or_else(|err| { @@ -50,10 +51,8 @@ fn parse_wild_samples() { // Make sure that the patch file we produce parses to the same information as the original // patch file. - let patch_file: String = patches - .iter() - .map(|patch| format!("{}\n", patch)) - .collect(); + #[allow(clippy::format_collect)] // Display::fmt is the only way to resolve Patch->str + let patch_file: String = patches.iter().map(|patch| format!("{}\n", patch)).collect(); let patches2 = Patch::from_multiple(&patch_file).unwrap_or_else(|err| { panic!( diff --git a/tests/wild-samples/.gitattributes b/tests/wild-samples/.gitattributes new file mode 100644 index 0000000..3e07e53 --- /dev/null +++ b/tests/wild-samples/.gitattributes @@ -0,0 +1,2 @@ +# Disable text processing on these wild sample - eg preserve their wild CRLF format +*.patch -text