mirror of
https://github.com/uutils/diffutils.git
synced 2026-06-10 15:48:59 -07:00
Compare commits
73 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c3b3f7dfa6 | |||
| 281098d751 | |||
| 34a5cc7340 | |||
| 6a152cdc7f | |||
| b8fada8faa | |||
| a213272d0c | |||
| 25e4a17421 | |||
| 589039ab4c | |||
| f83fccb542 | |||
| 76c4714f78 | |||
| b135b6f218 | |||
| 314e3a7320 | |||
| 6a73657b3a | |||
| e9f0630aaf | |||
| e6a0ba28c5 | |||
| 0ab824abda | |||
| f60fefaf6e | |||
| 14e77548fd | |||
| f2fd2127ed | |||
| cfc68d58bc | |||
| e0283083f2 | |||
| 8d65c2badd | |||
| a304ac0a68 | |||
| f916f1ce86 | |||
| 4ed7ea1553 | |||
| 62e10c6d6c | |||
| c68d386170 | |||
| a89f30afa0 | |||
| 0a67bf9fb8 | |||
| 1241db4806 | |||
| 3bc8668f78 | |||
| c90eee442f | |||
| 6c29f02527 | |||
| 790ef1e633 | |||
| 4c1a752f11 | |||
| 54a5407bec | |||
| 02632e915c | |||
| 3f9556aa05 | |||
| a94c6a60cf | |||
| c28973c019 | |||
| a660f7440c | |||
| 6a69a39852 | |||
| bf13d528be | |||
| 329a7e1f4a | |||
| e5de3cd93e | |||
| 0e14b37e38 | |||
| 3c1176082e | |||
| 865f97c78d | |||
| f5b65a5720 | |||
| 1372c5386c | |||
| d891e1034d | |||
| fdc35f6b8e | |||
| 6648963df1 | |||
| a10ef621c8 | |||
| 7939749338 | |||
| 7b3001f1ff | |||
| 416a4be06c | |||
| 9084134f04 | |||
| f42fc82f18 | |||
| 0b2505d249 | |||
| 045435b803 | |||
| b55cbf2ca2 | |||
| 12f3f16792 | |||
| e9e69b86db | |||
| 029d747e14 | |||
| e38055e5b2 | |||
| 61cfe6eec4 | |||
| ea9376aaaf | |||
| 582259a867 | |||
| da05a5254b | |||
| a4f7642d7a | |||
| e72ea046b7 | |||
| 2e84164d2f |
@@ -0,0 +1,285 @@
|
|||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
name: Basic CI
|
||||||
|
|
||||||
|
env:
|
||||||
|
CARGO_TERM_COLOR: always
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check:
|
||||||
|
name: cargo check
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
os: [ubuntu-latest, macOS-latest, windows-latest]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
|
- run: cargo check
|
||||||
|
|
||||||
|
test:
|
||||||
|
name: cargo test
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
os: [ubuntu-latest, macOS-latest, windows-latest]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
|
- run: cargo test
|
||||||
|
|
||||||
|
fmt:
|
||||||
|
name: cargo fmt --all -- --check
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
|
- run: rustup component add rustfmt
|
||||||
|
- run: cargo fmt --all -- --check
|
||||||
|
|
||||||
|
clippy:
|
||||||
|
name: cargo clippy -- -D warnings
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
os: [ubuntu-latest, macOS-latest, windows-latest]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
|
- run: rustup component add clippy
|
||||||
|
- run: cargo clippy -- -D warnings
|
||||||
|
|
||||||
|
gnu-testsuite:
|
||||||
|
name: GNU test suite
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
|
- run: cargo build --release
|
||||||
|
# do not fail, the report is merely informative (at least until all tests pass reliably)
|
||||||
|
- run: ./tests/run-upstream-testsuite.sh release || true
|
||||||
|
env:
|
||||||
|
TERM: xterm
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: test-results.json
|
||||||
|
path: tests/test-results.json
|
||||||
|
- run: ./tests/print-test-results.sh tests/test-results.json
|
||||||
|
|
||||||
|
build:
|
||||||
|
name: build ${{ matrix.binary || 'findutils' }} ${{ matrix.target }}
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
container: ${{ fromJson(matrix.container || '{"image":null}') }}
|
||||||
|
timeout-minutes: 30
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- os: ubuntu-20.04
|
||||||
|
target: x86_64-unknown-linux-musl
|
||||||
|
- os: ubuntu-20.04
|
||||||
|
target: aarch64-unknown-linux-musl
|
||||||
|
- os: ubuntu-20.04
|
||||||
|
target: armv7-unknown-linux-musleabi
|
||||||
|
container: '{"image": "messense/rust-musl-cross:armv7-musleabi"}'
|
||||||
|
- os: ubuntu-20.04
|
||||||
|
target: i686-unknown-linux-musl
|
||||||
|
container: '{"image": "messense/rust-musl-cross:i686-musl"}'
|
||||||
|
- os: macOS-11
|
||||||
|
target: x86_64-apple-darwin
|
||||||
|
macosx_deployment_target: 10.13
|
||||||
|
developer_dir: /Applications/Xcode_11.7.app
|
||||||
|
sdkroot: /Applications/Xcode_11.7.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk
|
||||||
|
- os: macos-14
|
||||||
|
target: aarch64-apple-darwin
|
||||||
|
macosx_deployment_target: 11.0
|
||||||
|
- os: windows-2019
|
||||||
|
target: x86_64-pc-windows-msvc
|
||||||
|
rustflags: -Ctarget-feature=+crt-static
|
||||||
|
steps:
|
||||||
|
- name: Clone repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install rust
|
||||||
|
uses: ./.github/actions/rust-toolchain
|
||||||
|
with:
|
||||||
|
toolchain: ${{ matrix.target == 'aarch64-apple-darwin' && 'beta' || 'stable' }}
|
||||||
|
target: ${{ matrix.target }}
|
||||||
|
if: ${{ !matrix.container }}
|
||||||
|
|
||||||
|
- name: Install musl-tools (x86_64)
|
||||||
|
run: sudo apt-get install musl-tools
|
||||||
|
if: ${{ matrix.target == 'x86_64-unknown-linux-musl' }}
|
||||||
|
|
||||||
|
- name: Install musl-tools (arm64)
|
||||||
|
run: |
|
||||||
|
set -x
|
||||||
|
sed 's/mirror+file:\/etc\/apt\/apt-mirrors\.txt/[arch=arm64] http:\/\/ports.ubuntu.com\/ubuntu-ports\//g' /etc/apt/sources.list | sudo tee /etc/apt/sources.list.d/ports.list
|
||||||
|
sudo dpkg --add-architecture arm64
|
||||||
|
sudo apt-get update || true
|
||||||
|
sudo apt-get install musl-dev:arm64 binutils-multiarch gcc-10-aarch64-linux-gnu libc6-dev-arm64-cross
|
||||||
|
apt-get download musl-tools:arm64
|
||||||
|
sudo dpkg-deb -x musl-tools_*_arm64.deb /
|
||||||
|
sed 2iREALGCC=aarch64-linux-gnu-gcc-10 /usr/bin/musl-gcc | sudo tee /usr/bin/aarch64-linux-musl-gcc > /dev/null
|
||||||
|
sudo chmod +x /usr/bin/aarch64-linux-musl-gcc
|
||||||
|
if: ${{ matrix.target == 'aarch64-unknown-linux-musl' }}
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: cargo build --locked --release --bin ${{ matrix.binary || 'findutils' }} --target ${{ matrix.target }} --features=openssl/vendored ${{ matrix.extra_args }}
|
||||||
|
env:
|
||||||
|
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER: aarch64-linux-musl-gcc
|
||||||
|
MACOSX_DEPLOYMENT_TARGET: ${{ matrix.macosx_deployment_target }}
|
||||||
|
DEVELOPER_DIR: ${{ matrix.developer_dir }}
|
||||||
|
SDKROOT: ${{ matrix.sdkroot }}
|
||||||
|
RUSTFLAGS: ${{ matrix.rustflags }}
|
||||||
|
|
||||||
|
# Workaround for the lack of substring() function in github actions expressions.
|
||||||
|
- name: Id
|
||||||
|
id: id
|
||||||
|
shell: bash
|
||||||
|
run: echo "id=${ID#refs/tags/}" >> $GITHUB_OUTPUT
|
||||||
|
env:
|
||||||
|
ID: ${{ startsWith(github.ref, 'refs/tags/') && github.ref || github.sha }}
|
||||||
|
|
||||||
|
- name: Upload artifacts
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: ${{ matrix.binary || 'findutils' }}-${{ steps.id.outputs.id }}-${{ matrix.target }}
|
||||||
|
path: target/${{ matrix.target }}/release/${{ matrix.binary || 'findutils' }}${{ endsWith(matrix.target, '-msvc') && '.exe' || '' }}
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
coverage:
|
||||||
|
name: Code Coverage
|
||||||
|
runs-on: ${{ matrix.job.os }}
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
job:
|
||||||
|
- { os: ubuntu-latest , features: unix }
|
||||||
|
- { os: macos-latest , features: macos }
|
||||||
|
- { os: windows-latest , features: windows }
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Initialize workflow variables
|
||||||
|
id: vars
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
## VARs setup
|
||||||
|
outputs() { step_id="vars"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo "${var}=${!var}" >> $GITHUB_OUTPUT; done; }
|
||||||
|
# toolchain
|
||||||
|
TOOLCHAIN="nightly" ## default to "nightly" toolchain (required for certain required unstable compiler flags) ## !maint: refactor when stable channel has needed support
|
||||||
|
# * specify gnu-type TOOLCHAIN for windows; `grcov` requires gnu-style code coverage data files
|
||||||
|
case ${{ matrix.job.os }} in windows-*) TOOLCHAIN="$TOOLCHAIN-x86_64-pc-windows-gnu" ;; esac;
|
||||||
|
# * use requested TOOLCHAIN if specified
|
||||||
|
if [ -n "${{ matrix.job.toolchain }}" ]; then TOOLCHAIN="${{ matrix.job.toolchain }}" ; fi
|
||||||
|
outputs TOOLCHAIN
|
||||||
|
# target-specific options
|
||||||
|
# * CARGO_FEATURES_OPTION
|
||||||
|
CARGO_FEATURES_OPTION='--all -- --check' ; ## default to '--all-features' for code coverage
|
||||||
|
# * CODECOV_FLAGS
|
||||||
|
CODECOV_FLAGS=$( echo "${{ matrix.job.os }}" | sed 's/[^[:alnum:]]/_/g' )
|
||||||
|
outputs CODECOV_FLAGS
|
||||||
|
|
||||||
|
- name: rust toolchain ~ install
|
||||||
|
uses: dtolnay/rust-toolchain@nightly
|
||||||
|
- name: Test
|
||||||
|
run: cargo test ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} --no-fail-fast
|
||||||
|
env:
|
||||||
|
CARGO_INCREMENTAL: "0"
|
||||||
|
RUSTC_WRAPPER: ""
|
||||||
|
RUSTFLAGS: "-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort"
|
||||||
|
RUSTDOCFLAGS: "-Cpanic=abort"
|
||||||
|
- name: "`grcov` ~ install"
|
||||||
|
id: build_grcov
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
git clone https://github.com/mozilla/grcov.git ~/grcov/
|
||||||
|
cd ~/grcov
|
||||||
|
# Hardcode the version of crossbeam-epoch. See
|
||||||
|
# https://github.com/uutils/coreutils/issues/3680
|
||||||
|
sed -i -e "s|tempfile =|crossbeam-epoch = \"=0.9.8\"\ntempfile =|" Cargo.toml
|
||||||
|
cargo install --path .
|
||||||
|
cd -
|
||||||
|
# Uncomment when the upstream issue
|
||||||
|
# https://github.com/mozilla/grcov/issues/849 is fixed
|
||||||
|
# uses: actions-rs/install@v0.1
|
||||||
|
# with:
|
||||||
|
# crate: grcov
|
||||||
|
# version: latest
|
||||||
|
# use-tool-cache: false
|
||||||
|
- name: Generate coverage data (via `grcov`)
|
||||||
|
id: coverage
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
## Generate coverage data
|
||||||
|
COVERAGE_REPORT_DIR="target/debug"
|
||||||
|
COVERAGE_REPORT_FILE="${COVERAGE_REPORT_DIR}/lcov.info"
|
||||||
|
mkdir -p "${COVERAGE_REPORT_DIR}"
|
||||||
|
# display coverage files
|
||||||
|
grcov . --output-type files --ignore build.rs --ignore "vendor/*" --ignore "/*" --ignore "[a-zA-Z]:/*" --excl-br-line "^\s*((debug_)?assert(_eq|_ne)?!|#\[derive\()" | sort --unique
|
||||||
|
# generate coverage report
|
||||||
|
grcov . --output-type lcov --output-path "${COVERAGE_REPORT_FILE}" --branch --ignore build.rs --ignore "vendor/*" --ignore "/*" --ignore "[a-zA-Z]:/*" --excl-br-line "^\s*((debug_)?assert(_eq|_ne)?!|#\[derive\()"
|
||||||
|
echo "report=${COVERAGE_REPORT_FILE}" >> $GITHUB_OUTPUT
|
||||||
|
- name: Upload coverage results (to Codecov.io)
|
||||||
|
uses: codecov/codecov-action@v3
|
||||||
|
# if: steps.vars.outputs.HAS_CODECOV_TOKEN
|
||||||
|
with:
|
||||||
|
# token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
file: ${{ steps.coverage.outputs.report }}
|
||||||
|
## flags: IntegrationTests, UnitTests, ${{ steps.vars.outputs.CODECOV_FLAGS }}
|
||||||
|
flags: ${{ steps.vars.outputs.CODECOV_FLAGS }}
|
||||||
|
name: codecov-umbrella
|
||||||
|
fail_ci_if_error: false
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
release:
|
||||||
|
name: release
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [build, clippy, check, test]
|
||||||
|
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
||||||
|
steps:
|
||||||
|
- name: Clone repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Check versions
|
||||||
|
run: |
|
||||||
|
tag_name=${GITHUB_REF#refs/tags/}
|
||||||
|
v=$(grep -m 1 "^version" Cargo.toml|sed -e "s|version = \"\(.*\)\"|\1|")
|
||||||
|
if ! echo $tag_name|grep -q $v; then
|
||||||
|
echo "Mistmatch of the version:"
|
||||||
|
echo "Cargo.toml says $v while the tag is $tag_name"
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Get artifacts
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
|
||||||
|
- name: Create release assets
|
||||||
|
run: |
|
||||||
|
for d in findutils-*; do
|
||||||
|
chmod +x "$d/findutils"*
|
||||||
|
cp README.md LICENSE "$d/"
|
||||||
|
tar -zcvf "$d.tar.gz" "$d"
|
||||||
|
echo -n "$(shasum -ba 256 "$d.tar.gz" | cut -d " " -f 1)" > "$d.tar.gz.sha256"
|
||||||
|
if [[ $d =~ (findutils-)(.*)?(x86_64-pc-windows)(.*)? ]]; then
|
||||||
|
zip -r "$d.zip" "$d"
|
||||||
|
echo -n "$(shasum -ba 256 "$d.zip" | cut -d " " -f 1)" > "$d.zip.sha256"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
- name: Create release
|
||||||
|
run: |
|
||||||
|
sudo apt-get update && sudo apt-get install -y hub
|
||||||
|
tag_name=${GITHUB_REF#refs/tags/}
|
||||||
|
for f in findutils-*.tar.gz* findutils-*.zip*; do
|
||||||
|
if [[ -f "$f" ]]; then
|
||||||
|
files="$files -a $f";
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
hub release create -m $tag_name $tag_name $files
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
name: Fuzzing
|
||||||
|
|
||||||
|
# spell-checker:ignore fuzzer
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
fuzz-build:
|
||||||
|
name: Build the fuzzers
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: dtolnay/rust-toolchain@nightly
|
||||||
|
- name: Install `cargo-fuzz`
|
||||||
|
run: cargo install cargo-fuzz
|
||||||
|
- uses: Swatinem/rust-cache@v2
|
||||||
|
with:
|
||||||
|
shared-key: "cargo-fuzz-cache-key"
|
||||||
|
cache-directories: "fuzz/target"
|
||||||
|
- name: Run `cargo-fuzz build`
|
||||||
|
run: cargo +nightly fuzz build
|
||||||
|
|
||||||
|
fuzz-run:
|
||||||
|
needs: fuzz-build
|
||||||
|
name: Run the fuzzers
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 5
|
||||||
|
env:
|
||||||
|
RUN_FOR: 60
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
test-target:
|
||||||
|
- { name: fuzz_ed, should_pass: true }
|
||||||
|
- { name: fuzz_normal, should_pass: true }
|
||||||
|
- { name: fuzz_patch, should_pass: true }
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: dtolnay/rust-toolchain@nightly
|
||||||
|
- name: Install `cargo-fuzz`
|
||||||
|
run: cargo install cargo-fuzz
|
||||||
|
- uses: Swatinem/rust-cache@v2
|
||||||
|
with:
|
||||||
|
shared-key: "cargo-fuzz-cache-key"
|
||||||
|
cache-directories: "fuzz/target"
|
||||||
|
- name: Restore Cached Corpus
|
||||||
|
uses: actions/cache/restore@v4
|
||||||
|
with:
|
||||||
|
key: corpus-cache-${{ matrix.test-target.name }}
|
||||||
|
path: |
|
||||||
|
fuzz/corpus/${{ matrix.test-target.name }}
|
||||||
|
- name: Run ${{ matrix.test-target.name }} for XX seconds
|
||||||
|
shell: bash
|
||||||
|
continue-on-error: ${{ !matrix.test-target.name.should_pass }}
|
||||||
|
run: |
|
||||||
|
cargo +nightly fuzz run ${{ matrix.test-target.name }} -- -max_total_time=${{ env.RUN_FOR }} -detect_leaks=0
|
||||||
|
- name: Save Corpus Cache
|
||||||
|
uses: actions/cache/save@v4
|
||||||
|
with:
|
||||||
|
key: corpus-cache-${{ matrix.test-target.name }}
|
||||||
|
path: |
|
||||||
|
fuzz/corpus/${{ matrix.test-target.name }}
|
||||||
@@ -1,3 +1,2 @@
|
|||||||
/target
|
/target
|
||||||
Cargo.lock
|
|
||||||
*.swp
|
*.swp
|
||||||
|
|||||||
Generated
+434
@@ -0,0 +1,434 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aho-corasick"
|
||||||
|
version = "1.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle"
|
||||||
|
version = "1.0.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "assert_cmd"
|
||||||
|
version = "2.0.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ed72493ac66d5804837f480ab3766c72bdfab91a65e565fc54fa9e42db0073a8"
|
||||||
|
dependencies = [
|
||||||
|
"anstyle",
|
||||||
|
"bstr",
|
||||||
|
"doc-comment",
|
||||||
|
"predicates",
|
||||||
|
"predicates-core",
|
||||||
|
"predicates-tree",
|
||||||
|
"wait-timeout",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "autocfg"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitflags"
|
||||||
|
version = "2.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bstr"
|
||||||
|
version = "1.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c48f0051a4b4c5e0b6d365cd04af53aeaa209e3cc15ec2cdb69e73cc87fbd0dc"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
"regex-automata",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "diff"
|
||||||
|
version = "0.1.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "difflib"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "diffutils"
|
||||||
|
version = "0.3.0"
|
||||||
|
dependencies = [
|
||||||
|
"assert_cmd",
|
||||||
|
"diff",
|
||||||
|
"predicates",
|
||||||
|
"pretty_assertions",
|
||||||
|
"regex",
|
||||||
|
"same-file",
|
||||||
|
"tempfile",
|
||||||
|
"unicode-width",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "doc-comment"
|
||||||
|
version = "0.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "errno"
|
||||||
|
version = "0.3.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fastrand"
|
||||||
|
version = "2.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "float-cmp"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.153"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "linux-raw-sys"
|
||||||
|
version = "0.4.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memchr"
|
||||||
|
version = "2.7.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "normalize-line-endings"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-traits"
|
||||||
|
version = "0.2.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "predicates"
|
||||||
|
version = "3.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "68b87bfd4605926cdfefc1c3b5f8fe560e3feca9d5552cf68c466d3d8236c7e8"
|
||||||
|
dependencies = [
|
||||||
|
"anstyle",
|
||||||
|
"difflib",
|
||||||
|
"float-cmp",
|
||||||
|
"normalize-line-endings",
|
||||||
|
"predicates-core",
|
||||||
|
"regex",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "predicates-core"
|
||||||
|
version = "1.0.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b794032607612e7abeb4db69adb4e33590fa6cf1149e95fd7cb00e634b92f174"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "predicates-tree"
|
||||||
|
version = "1.0.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "368ba315fb8c5052ab692e68a0eefec6ec57b23a36959c14496f0b0df2c0cecf"
|
||||||
|
dependencies = [
|
||||||
|
"predicates-core",
|
||||||
|
"termtree",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pretty_assertions"
|
||||||
|
version = "1.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66"
|
||||||
|
dependencies = [
|
||||||
|
"diff",
|
||||||
|
"yansi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.78"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.35"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex"
|
||||||
|
version = "1.10.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
|
"regex-automata",
|
||||||
|
"regex-syntax",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-automata"
|
||||||
|
version = "0.4.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
|
"regex-syntax",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-syntax"
|
||||||
|
version = "0.8.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustix"
|
||||||
|
version = "0.38.31"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"errno",
|
||||||
|
"libc",
|
||||||
|
"linux-raw-sys",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "same-file"
|
||||||
|
version = "1.0.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
|
||||||
|
dependencies = [
|
||||||
|
"winapi-util",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde"
|
||||||
|
version = "1.0.197"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2"
|
||||||
|
dependencies = [
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_derive"
|
||||||
|
version = "1.0.197"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "2.0.50"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "74f1bdc9872430ce9b75da68329d1c1746faf50ffac5f19e02b71e37ff881ffb"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tempfile"
|
||||||
|
version = "3.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a365e8cd18e44762ef95d87f284f4b5cd04107fec2ff3052bd6a3e6069669e67"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"fastrand",
|
||||||
|
"rustix",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "termtree"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-ident"
|
||||||
|
version = "1.0.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-width"
|
||||||
|
version = "0.1.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wait-timeout"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi"
|
||||||
|
version = "0.3.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||||
|
dependencies = [
|
||||||
|
"winapi-i686-pc-windows-gnu",
|
||||||
|
"winapi-x86_64-pc-windows-gnu",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-i686-pc-windows-gnu"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-util"
|
||||||
|
version = "0.1.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
|
||||||
|
dependencies = [
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-x86_64-pc-windows-gnu"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.52.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||||
|
dependencies = [
|
||||||
|
"windows-targets",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-targets"
|
||||||
|
version = "0.52.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d98532992affa02e52709d5b4d145a3668ae10d9081eea4a7f26f719a8476f71"
|
||||||
|
dependencies = [
|
||||||
|
"windows_aarch64_gnullvm",
|
||||||
|
"windows_aarch64_msvc",
|
||||||
|
"windows_i686_gnu",
|
||||||
|
"windows_i686_msvc",
|
||||||
|
"windows_x86_64_gnu",
|
||||||
|
"windows_x86_64_gnullvm",
|
||||||
|
"windows_x86_64_msvc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_gnullvm"
|
||||||
|
version = "0.52.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e7269c1442e75af9fa59290383f7665b828efc76c429cc0b7f2ecb33cf51ebae"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_msvc"
|
||||||
|
version = "0.52.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f70ab2cebf332b7ecbdd98900c2da5298a8c862472fb35c75fc297eabb9d89b8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_gnu"
|
||||||
|
version = "0.52.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "679f235acf6b1639408c0f6db295697a19d103b0cdc88146aa1b992c580c647d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_msvc"
|
||||||
|
version = "0.52.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3480ac194b55ae274a7e135c21645656825da4a7f5b6e9286291b2113c94a78b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnu"
|
||||||
|
version = "0.52.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "42c46bab241c121402d1cb47d028ea3680ee2f359dcc287482dcf7fdddc73363"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnullvm"
|
||||||
|
version = "0.52.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dc885a4332ee1afb9a1bacf11514801011725570d35675abc229ce7e3afe4d20"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_msvc"
|
||||||
|
version = "0.52.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9e440c60457f84b0bee09208e62acc7ade264b38c4453f6312b8c9ab1613e73c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "yansi"
|
||||||
|
version = "0.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec"
|
||||||
+20
-11
@@ -1,18 +1,27 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "unified-diff"
|
name = "diffutils"
|
||||||
version = "0.2.1"
|
version = "0.3.0"
|
||||||
authors = [
|
edition = "2021"
|
||||||
"Michael Howell <michael@notriddle.com>",
|
description = "A CLI app for generating diff files"
|
||||||
"The Rust Project Developers"
|
|
||||||
]
|
|
||||||
edition = "2018"
|
|
||||||
description = "An implementation of the GNU unified diff format"
|
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
repository = "https://github.com/notriddle/rust-unified-diff"
|
repository = "https://github.com/uutils/diffutils"
|
||||||
exclude = [ "fuzz" ]
|
|
||||||
|
[lib]
|
||||||
|
name = "diffutilslib"
|
||||||
|
path = "src/lib.rs"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "unified-diff"
|
name = "diffutils"
|
||||||
|
path = "src/main.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
diff = "0.1.10"
|
diff = "0.1.10"
|
||||||
|
regex = "1.10.3"
|
||||||
|
same-file = "1.0.6"
|
||||||
|
unicode-width = "0.1.11"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
pretty_assertions = "1"
|
||||||
|
assert_cmd = "2.0.14"
|
||||||
|
predicates = "3.1.0"
|
||||||
|
tempfile = "3.10.0"
|
||||||
|
|||||||
@@ -1,41 +1,56 @@
|
|||||||
A GNU unified diff generator. Oracle tested against GNU patch 2.7.6
|
[](https://crates.io/crates/diffutils)
|
||||||
|
[](https://discord.gg/wQVJbvJ)
|
||||||
|
[](https://github.com/uutils/diffutils/blob/main/LICENSE)
|
||||||
|
[](https://deps.rs/repo/github/uutils/diffutils)
|
||||||
|
|
||||||
Based on the incomplete diff generator in https://github.com/rust-lang/rust/blob/master/src/tools/compiletest/src/runtest.rs,
|
[](https://codecov.io/gh/uutils/diffutils)
|
||||||
but it implements a different format.
|
|
||||||
|
|
||||||
|
The goal of this package is to be a drop-in replacement for the [diffutils commands](https://www.gnu.org/software/diffutils/) in Rust.
|
||||||
|
|
||||||
|
Based on the incomplete diff generator in https://github.com/rust-lang/rust/blob/master/src/tools/compiletest/src/runtest.rs, and made to be compatible with GNU's diff and patch tools.
|
||||||
|
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Ensure you have Rust installed on your system. You can install Rust through [rustup](https://rustup.rs/).
|
||||||
|
|
||||||
|
Clone the repository and build the project using Cargo:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/uutils/diffutils.git
|
||||||
|
cd diffutils
|
||||||
|
cargo build --release
|
||||||
```
|
```
|
||||||
~/unified-diff$ cargo run Cargo.lock Cargo.toml
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
```bash
|
||||||
|
|
||||||
|
cat <<EOF >fruits_old.txt
|
||||||
|
Apple
|
||||||
|
Banana
|
||||||
|
Cherry
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat <<EOF >fruits_new.txt
|
||||||
|
Apple
|
||||||
|
Fig
|
||||||
|
Cherry
|
||||||
|
EOF
|
||||||
|
|
||||||
|
$ cargo run -- -u fruits_old.txt fruits_new.txt
|
||||||
Finished dev [unoptimized + debuginfo] target(s) in 0.00s
|
Finished dev [unoptimized + debuginfo] target(s) in 0.00s
|
||||||
Running `target/debug/unified-diff Cargo.lock Cargo.toml`
|
Running `target/debug/diffutils -u fruits_old.txt fruits_new.txt`
|
||||||
--- Cargo.lock
|
--- fruits_old.txt
|
||||||
+++ Cargo.toml
|
+++ fruits_new.txt
|
||||||
@@ -1,14 +1,14 @@
|
@@ -1,3 +1,3 @@
|
||||||
-# This file is automatically @generated by Cargo.
|
Apple
|
||||||
-# It is not intended for manual editing.
|
-Banana
|
||||||
-[[package]]
|
+Fig
|
||||||
-name = "diff"
|
Cherry
|
||||||
-version = "0.1.12"
|
|
||||||
-source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
-checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499"
|
|
||||||
-
|
|
||||||
-[[package]]
|
|
||||||
+[package]
|
|
||||||
name = "unified-diff"
|
|
||||||
version = "0.1.0"
|
|
||||||
-dependencies = [
|
|
||||||
- "diff",
|
|
||||||
+authors = [
|
|
||||||
+ "Michael Howell <michael@notriddle.com>",
|
|
||||||
+ "The Rust Project Developers"
|
|
||||||
]
|
|
||||||
+edition = "2018"
|
|
||||||
+
|
|
||||||
+[[bin]]
|
|
||||||
+name = "unified-diff"
|
|
||||||
+
|
|
||||||
+[dependencies]
|
|
||||||
+diff = "0.1.10"
|
|
||||||
~/unified-diff$ rustup override set nightly
|
|
||||||
~/unified-diff$ cargo fuzz run fuzz_patch
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
diffutils is licensed under the MIT and Apache Licenses - see the `LICENSE-MIT` or `LICENSE-APACHE` files for details
|
||||||
|
|||||||
+16
-5
@@ -2,7 +2,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "unified-diff-fuzz"
|
name = "unified-diff-fuzz"
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
authors = ["Automatically generated"]
|
|
||||||
publish = false
|
publish = false
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
@@ -10,10 +9,8 @@ edition = "2018"
|
|||||||
cargo-fuzz = true
|
cargo-fuzz = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
libfuzzer-sys = "0.3"
|
libfuzzer-sys = "0.4"
|
||||||
|
diffutils = { path = "../" }
|
||||||
[dependencies.unified-diff]
|
|
||||||
path = ".."
|
|
||||||
|
|
||||||
# Prevent this from interfering with workspaces
|
# Prevent this from interfering with workspaces
|
||||||
[workspace]
|
[workspace]
|
||||||
@@ -25,3 +22,17 @@ path = "fuzz_targets/fuzz_patch.rs"
|
|||||||
test = false
|
test = false
|
||||||
doc = false
|
doc = false
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "fuzz_normal"
|
||||||
|
path = "fuzz_targets/fuzz_normal.rs"
|
||||||
|
test = false
|
||||||
|
doc = false
|
||||||
|
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "fuzz_ed"
|
||||||
|
path = "fuzz_targets/fuzz_ed.rs"
|
||||||
|
test = false
|
||||||
|
doc = false
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
#![no_main]
|
||||||
|
#[macro_use]
|
||||||
|
extern crate libfuzzer_sys;
|
||||||
|
use diffutilslib::ed_diff;
|
||||||
|
use diffutilslib::ed_diff::DiffError;
|
||||||
|
use diffutilslib::params::Params;
|
||||||
|
use std::fs::{self, File};
|
||||||
|
use std::io::Write;
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
fn diff_w(expected: &[u8], actual: &[u8], filename: &str) -> Result<Vec<u8>, DiffError> {
|
||||||
|
let mut output = ed_diff::diff(expected, actual, &Params::default())?;
|
||||||
|
writeln!(&mut output, "w {filename}").unwrap();
|
||||||
|
Ok(output)
|
||||||
|
}
|
||||||
|
|
||||||
|
fuzz_target!(|x: (Vec<u8>, Vec<u8>)| {
|
||||||
|
let (mut from, mut to) = x;
|
||||||
|
from.push(b'\n');
|
||||||
|
to.push(b'\n');
|
||||||
|
if let Ok(s) = String::from_utf8(from.clone()) {
|
||||||
|
if !s.is_ascii() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if s.find(|x| x < ' ' && x != '\n').is_some() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if let Ok(s) = String::from_utf8(to.clone()) {
|
||||||
|
if !s.is_ascii() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if s.find(|x| x < ' ' && x != '\n').is_some() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let diff = diff_w(&from, &to, "target/fuzz.file").unwrap();
|
||||||
|
File::create("target/fuzz.file.original")
|
||||||
|
.unwrap()
|
||||||
|
.write_all(&from)
|
||||||
|
.unwrap();
|
||||||
|
File::create("target/fuzz.file.expected")
|
||||||
|
.unwrap()
|
||||||
|
.write_all(&to)
|
||||||
|
.unwrap();
|
||||||
|
File::create("target/fuzz.file")
|
||||||
|
.unwrap()
|
||||||
|
.write_all(&from)
|
||||||
|
.unwrap();
|
||||||
|
File::create("target/fuzz.ed")
|
||||||
|
.unwrap()
|
||||||
|
.write_all(&diff)
|
||||||
|
.unwrap();
|
||||||
|
let output = Command::new("ed")
|
||||||
|
.arg("target/fuzz.file")
|
||||||
|
.stdin(File::open("target/fuzz.ed").unwrap())
|
||||||
|
.output()
|
||||||
|
.unwrap();
|
||||||
|
if !output.status.success() {
|
||||||
|
panic!(
|
||||||
|
"STDOUT:\n{}\nSTDERR:\n{}",
|
||||||
|
String::from_utf8_lossy(&output.stdout),
|
||||||
|
String::from_utf8_lossy(&output.stderr)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let result = fs::read("target/fuzz.file").unwrap();
|
||||||
|
if result != to {
|
||||||
|
panic!(
|
||||||
|
"STDOUT:\n{}\nSTDERR:\n{}",
|
||||||
|
String::from_utf8_lossy(&output.stdout),
|
||||||
|
String::from_utf8_lossy(&output.stderr)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
#![no_main]
|
||||||
|
#[macro_use]
|
||||||
|
extern crate libfuzzer_sys;
|
||||||
|
use diffutilslib::normal_diff;
|
||||||
|
use diffutilslib::params::Params;
|
||||||
|
|
||||||
|
use std::fs::{self, File};
|
||||||
|
use std::io::Write;
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
fuzz_target!(|x: (Vec<u8>, Vec<u8>)| {
|
||||||
|
let (from, to) = x;
|
||||||
|
/*if let Ok(s) = String::from_utf8(from.clone()) {
|
||||||
|
if !s.is_ascii() { return }
|
||||||
|
if s.find(|x| x < ' ' && x != '\n').is_some() { return }
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if let Ok(s) = String::from_utf8(to.clone()) {
|
||||||
|
if !s.is_ascii() { return }
|
||||||
|
if s.find(|x| x < ' ' && x != '\n').is_some() { return }
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}*/
|
||||||
|
let diff = normal_diff::diff(&from, &to, &Params::default());
|
||||||
|
File::create("target/fuzz.file.original")
|
||||||
|
.unwrap()
|
||||||
|
.write_all(&from)
|
||||||
|
.unwrap();
|
||||||
|
File::create("target/fuzz.file.expected")
|
||||||
|
.unwrap()
|
||||||
|
.write_all(&to)
|
||||||
|
.unwrap();
|
||||||
|
File::create("target/fuzz.file")
|
||||||
|
.unwrap()
|
||||||
|
.write_all(&from)
|
||||||
|
.unwrap();
|
||||||
|
File::create("target/fuzz.diff")
|
||||||
|
.unwrap()
|
||||||
|
.write_all(&diff)
|
||||||
|
.unwrap();
|
||||||
|
let output = Command::new("patch")
|
||||||
|
.arg("-p0")
|
||||||
|
.arg("--binary")
|
||||||
|
.arg("--fuzz=0")
|
||||||
|
.arg("--normal")
|
||||||
|
.arg("target/fuzz.file")
|
||||||
|
.stdin(File::open("target/fuzz.diff").unwrap())
|
||||||
|
.output()
|
||||||
|
.unwrap();
|
||||||
|
if !output.status.success() {
|
||||||
|
panic!(
|
||||||
|
"STDOUT:\n{}\nSTDERR:\n{}",
|
||||||
|
String::from_utf8_lossy(&output.stdout),
|
||||||
|
String::from_utf8_lossy(&output.stderr)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let result = fs::read("target/fuzz.file").unwrap();
|
||||||
|
if result != to {
|
||||||
|
panic!(
|
||||||
|
"STDOUT:\n{}\nSTDERR:\n{}",
|
||||||
|
String::from_utf8_lossy(&output.stdout),
|
||||||
|
String::from_utf8_lossy(&output.stderr)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
#![no_main]
|
#![no_main]
|
||||||
#[macro_use] extern crate libfuzzer_sys;
|
#[macro_use]
|
||||||
extern crate unified_diff;
|
extern crate libfuzzer_sys;
|
||||||
|
use diffutilslib::params::Params;
|
||||||
|
use diffutilslib::unified_diff;
|
||||||
use std::fs::{self, File};
|
use std::fs::{self, File};
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
@@ -20,7 +21,16 @@ fuzz_target!(|x: (Vec<u8>, Vec<u8>, u8)| {
|
|||||||
} else {
|
} else {
|
||||||
return
|
return
|
||||||
}*/
|
}*/
|
||||||
let diff = unified_diff::diff(&from, "a/fuzz.file", &to, "target/fuzz.file", context as usize);
|
let diff = unified_diff::diff(
|
||||||
|
&from,
|
||||||
|
&to,
|
||||||
|
&Params {
|
||||||
|
from: "a/fuzz.file".into(),
|
||||||
|
to: "target/fuzz.file".into(),
|
||||||
|
context_count: context as usize,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
);
|
||||||
File::create("target/fuzz.file.original")
|
File::create("target/fuzz.file.original")
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.write_all(&from)
|
.write_all(&from)
|
||||||
@@ -45,11 +55,18 @@ fuzz_target!(|x: (Vec<u8>, Vec<u8>, u8)| {
|
|||||||
.output()
|
.output()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
if !output.status.success() {
|
if !output.status.success() {
|
||||||
panic!("STDOUT:\n{}\nSTDERR:\n{}", String::from_utf8_lossy(&output.stdout), String::from_utf8_lossy(&output.stderr));
|
panic!(
|
||||||
|
"STDOUT:\n{}\nSTDERR:\n{}",
|
||||||
|
String::from_utf8_lossy(&output.stdout),
|
||||||
|
String::from_utf8_lossy(&output.stderr)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
let result = fs::read("target/fuzz.file").unwrap();
|
let result = fs::read("target/fuzz.file").unwrap();
|
||||||
if result != to {
|
if result != to {
|
||||||
panic!("STDOUT:\n{}\nSTDERR:\n{}", String::from_utf8_lossy(&output.stdout), String::from_utf8_lossy(&output.stderr));
|
panic!(
|
||||||
|
"STDOUT:\n{}\nSTDERR:\n{}",
|
||||||
|
String::from_utf8_lossy(&output.stdout),
|
||||||
|
String::from_utf8_lossy(&output.stderr)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||||
|
"extends": [
|
||||||
|
"config:recommended"
|
||||||
|
]
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
+440
@@ -0,0 +1,440 @@
|
|||||||
|
// This file is part of the uutils diffutils package.
|
||||||
|
//
|
||||||
|
// For the full copyright and license information, please view the LICENSE-*
|
||||||
|
// files that was distributed with this source code.
|
||||||
|
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
use crate::params::Params;
|
||||||
|
use crate::utils::do_write_line;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
struct Mismatch {
|
||||||
|
pub line_number_expected: usize,
|
||||||
|
pub line_number_actual: usize,
|
||||||
|
pub expected: Vec<Vec<u8>>,
|
||||||
|
pub actual: Vec<Vec<u8>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
pub enum DiffError {
|
||||||
|
MissingNL,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for DiffError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
|
||||||
|
std::fmt::Display::fmt("No newline at end of file", f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<DiffError> for String {
|
||||||
|
fn from(_: DiffError) -> String {
|
||||||
|
"No newline at end of file".into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Mismatch {
|
||||||
|
fn new(line_number_expected: usize, line_number_actual: usize) -> Mismatch {
|
||||||
|
Mismatch {
|
||||||
|
line_number_expected,
|
||||||
|
line_number_actual,
|
||||||
|
expected: Vec::new(),
|
||||||
|
actual: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Produces a diff between the expected output and actual output.
|
||||||
|
fn make_diff(expected: &[u8], actual: &[u8], stop_early: bool) -> Result<Vec<Mismatch>, DiffError> {
|
||||||
|
let mut line_number_expected = 1;
|
||||||
|
let mut line_number_actual = 1;
|
||||||
|
let mut results = Vec::new();
|
||||||
|
let mut mismatch = Mismatch::new(line_number_expected, line_number_actual);
|
||||||
|
|
||||||
|
let mut expected_lines: Vec<&[u8]> = expected.split(|&c| c == b'\n').collect();
|
||||||
|
let mut actual_lines: Vec<&[u8]> = actual.split(|&c| c == b'\n').collect();
|
||||||
|
|
||||||
|
debug_assert_eq!(b"".split(|&c| c == b'\n').count(), 1);
|
||||||
|
// ^ means that underflow here is impossible
|
||||||
|
let _expected_lines_count = expected_lines.len() - 1;
|
||||||
|
let _actual_lines_count = actual_lines.len() - 1;
|
||||||
|
|
||||||
|
if expected_lines.last() == Some(&&b""[..]) {
|
||||||
|
expected_lines.pop();
|
||||||
|
} else {
|
||||||
|
return Err(DiffError::MissingNL);
|
||||||
|
}
|
||||||
|
|
||||||
|
if actual_lines.last() == Some(&&b""[..]) {
|
||||||
|
actual_lines.pop();
|
||||||
|
} else {
|
||||||
|
return Err(DiffError::MissingNL);
|
||||||
|
}
|
||||||
|
|
||||||
|
for result in diff::slice(&expected_lines, &actual_lines) {
|
||||||
|
match result {
|
||||||
|
diff::Result::Left(str) => {
|
||||||
|
if !mismatch.actual.is_empty() {
|
||||||
|
results.push(mismatch);
|
||||||
|
mismatch = Mismatch::new(line_number_expected, line_number_actual);
|
||||||
|
}
|
||||||
|
mismatch.expected.push(str.to_vec());
|
||||||
|
line_number_expected += 1;
|
||||||
|
}
|
||||||
|
diff::Result::Right(str) => {
|
||||||
|
mismatch.actual.push(str.to_vec());
|
||||||
|
line_number_actual += 1;
|
||||||
|
}
|
||||||
|
diff::Result::Both(_str, _) => {
|
||||||
|
line_number_expected += 1;
|
||||||
|
line_number_actual += 1;
|
||||||
|
if !mismatch.actual.is_empty() || !mismatch.expected.is_empty() {
|
||||||
|
results.push(mismatch);
|
||||||
|
mismatch = Mismatch::new(line_number_expected, line_number_actual);
|
||||||
|
} else {
|
||||||
|
mismatch.line_number_expected = line_number_expected;
|
||||||
|
mismatch.line_number_actual = line_number_actual;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if stop_early && !results.is_empty() {
|
||||||
|
// Optimization: stop analyzing the files as soon as there are any differences
|
||||||
|
return Ok(results);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !mismatch.actual.is_empty() || !mismatch.expected.is_empty() {
|
||||||
|
results.push(mismatch);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(results)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn diff(expected: &[u8], actual: &[u8], params: &Params) -> Result<Vec<u8>, DiffError> {
|
||||||
|
let mut output = Vec::new();
|
||||||
|
let diff_results = make_diff(expected, actual, params.brief)?;
|
||||||
|
if params.brief && !diff_results.is_empty() {
|
||||||
|
write!(&mut output, "\0").unwrap();
|
||||||
|
return Ok(output);
|
||||||
|
}
|
||||||
|
let mut lines_offset = 0;
|
||||||
|
for result in diff_results {
|
||||||
|
let line_number_expected: isize = result.line_number_expected as isize + lines_offset;
|
||||||
|
let _line_number_actual: isize = result.line_number_actual as isize + lines_offset;
|
||||||
|
let expected_count: isize = result.expected.len() as isize;
|
||||||
|
let actual_count: isize = result.actual.len() as isize;
|
||||||
|
match (expected_count, actual_count) {
|
||||||
|
(0, 0) => unreachable!(),
|
||||||
|
(0, _) => writeln!(&mut output, "{}a", line_number_expected - 1).unwrap(),
|
||||||
|
(_, 0) => writeln!(
|
||||||
|
&mut output,
|
||||||
|
"{},{}d",
|
||||||
|
line_number_expected,
|
||||||
|
expected_count + line_number_expected - 1
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
(1, _) => writeln!(&mut output, "{line_number_expected}c").unwrap(),
|
||||||
|
_ => writeln!(
|
||||||
|
&mut output,
|
||||||
|
"{},{}c",
|
||||||
|
line_number_expected,
|
||||||
|
expected_count + line_number_expected - 1
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
}
|
||||||
|
lines_offset += actual_count - expected_count;
|
||||||
|
if actual_count != 0 {
|
||||||
|
for actual in &result.actual {
|
||||||
|
if actual == b"." {
|
||||||
|
writeln!(&mut output, "..\n.\ns/.//\na").unwrap();
|
||||||
|
} else {
|
||||||
|
do_write_line(&mut output, actual, params.expand_tabs, params.tabsize).unwrap();
|
||||||
|
writeln!(&mut output).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writeln!(&mut output, ".").unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(output)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
pub fn diff_w(expected: &[u8], actual: &[u8], filename: &str) -> Result<Vec<u8>, DiffError> {
|
||||||
|
let mut output = diff(expected, actual, &Params::default())?;
|
||||||
|
writeln!(&mut output, "w {filename}").unwrap();
|
||||||
|
Ok(output)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_basic() {
|
||||||
|
let from = b"a\n";
|
||||||
|
let to = b"b\n";
|
||||||
|
let diff = diff(from, to, &Params::default()).unwrap();
|
||||||
|
let expected = ["1c", "b", ".", ""].join("\n");
|
||||||
|
assert_eq!(diff, expected.as_bytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_permutations() {
|
||||||
|
let target = "target/ed-diff/";
|
||||||
|
// test all possible six-line files.
|
||||||
|
let _ = std::fs::create_dir(target);
|
||||||
|
for &a in &[0, 1, 2] {
|
||||||
|
for &b in &[0, 1, 2] {
|
||||||
|
for &c in &[0, 1, 2] {
|
||||||
|
for &d in &[0, 1, 2] {
|
||||||
|
for &e in &[0, 1, 2] {
|
||||||
|
for &f in &[0, 1, 2] {
|
||||||
|
use std::fs::{self, File};
|
||||||
|
use std::io::Write;
|
||||||
|
use std::process::Command;
|
||||||
|
let mut alef = Vec::new();
|
||||||
|
let mut bet = Vec::new();
|
||||||
|
alef.write_all(if a == 0 { b"a\n" } else { b"b\n" })
|
||||||
|
.unwrap();
|
||||||
|
if a != 2 {
|
||||||
|
bet.write_all(b"b\n").unwrap();
|
||||||
|
}
|
||||||
|
alef.write_all(if b == 0 { b"c\n" } else { b"d\n" })
|
||||||
|
.unwrap();
|
||||||
|
if b != 2 {
|
||||||
|
bet.write_all(b"d\n").unwrap();
|
||||||
|
}
|
||||||
|
alef.write_all(if c == 0 { b"e\n" } else { b"f\n" })
|
||||||
|
.unwrap();
|
||||||
|
if c != 2 {
|
||||||
|
bet.write_all(b"f\n").unwrap();
|
||||||
|
}
|
||||||
|
alef.write_all(if d == 0 { b"g\n" } else { b"h\n" })
|
||||||
|
.unwrap();
|
||||||
|
if d != 2 {
|
||||||
|
bet.write_all(b"h\n").unwrap();
|
||||||
|
}
|
||||||
|
alef.write_all(if e == 0 { b"i\n" } else { b"j\n" })
|
||||||
|
.unwrap();
|
||||||
|
if e != 2 {
|
||||||
|
bet.write_all(b"j\n").unwrap();
|
||||||
|
}
|
||||||
|
alef.write_all(if f == 0 { b"k\n" } else { b"l\n" })
|
||||||
|
.unwrap();
|
||||||
|
if f != 2 {
|
||||||
|
bet.write_all(b"l\n").unwrap();
|
||||||
|
}
|
||||||
|
// This test diff is intentionally reversed.
|
||||||
|
// We want it to turn the alef into bet.
|
||||||
|
let diff = diff_w(&alef, &bet, &format!("{target}/alef")).unwrap();
|
||||||
|
File::create("target/ab.ed")
|
||||||
|
.unwrap()
|
||||||
|
.write_all(&diff)
|
||||||
|
.unwrap();
|
||||||
|
let mut fa = File::create(&format!("{target}/alef")).unwrap();
|
||||||
|
fa.write_all(&alef[..]).unwrap();
|
||||||
|
let mut fb = File::create(&format!("{target}/bet")).unwrap();
|
||||||
|
fb.write_all(&bet[..]).unwrap();
|
||||||
|
let _ = fa;
|
||||||
|
let _ = fb;
|
||||||
|
let output = Command::new("ed")
|
||||||
|
.arg(&format!("{target}/alef"))
|
||||||
|
.stdin(File::open("target/ab.ed").unwrap())
|
||||||
|
.output()
|
||||||
|
.unwrap();
|
||||||
|
assert!(output.status.success(), "{output:?}");
|
||||||
|
//println!("{}", String::from_utf8_lossy(&output.stdout));
|
||||||
|
//println!("{}", String::from_utf8_lossy(&output.stderr));
|
||||||
|
let alef = fs::read(&format!("{target}/alef")).unwrap();
|
||||||
|
assert_eq!(alef, bet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_permutations_empty_lines() {
|
||||||
|
let target = "target/ed-diff/";
|
||||||
|
// test all possible six-line files with missing newlines.
|
||||||
|
let _ = std::fs::create_dir(target);
|
||||||
|
for &a in &[0, 1, 2] {
|
||||||
|
for &b in &[0, 1, 2] {
|
||||||
|
for &c in &[0, 1, 2] {
|
||||||
|
for &d in &[0, 1, 2] {
|
||||||
|
for &e in &[0, 1, 2] {
|
||||||
|
for &f in &[0, 1, 2] {
|
||||||
|
use std::fs::{self, File};
|
||||||
|
use std::io::Write;
|
||||||
|
use std::process::Command;
|
||||||
|
let mut alef = Vec::new();
|
||||||
|
let mut bet = Vec::new();
|
||||||
|
alef.write_all(if a == 0 { b"\n" } else { b"b\n" }).unwrap();
|
||||||
|
if a != 2 {
|
||||||
|
bet.write_all(b"b\n").unwrap();
|
||||||
|
}
|
||||||
|
alef.write_all(if b == 0 { b"\n" } else { b"d\n" }).unwrap();
|
||||||
|
if b != 2 {
|
||||||
|
bet.write_all(b"d\n").unwrap();
|
||||||
|
}
|
||||||
|
alef.write_all(if c == 0 { b"\n" } else { b"f\n" }).unwrap();
|
||||||
|
if c != 2 {
|
||||||
|
bet.write_all(b"f\n").unwrap();
|
||||||
|
}
|
||||||
|
alef.write_all(if d == 0 { b"\n" } else { b"h\n" }).unwrap();
|
||||||
|
if d != 2 {
|
||||||
|
bet.write_all(b"h\n").unwrap();
|
||||||
|
}
|
||||||
|
alef.write_all(if e == 0 { b"\n" } else { b"j\n" }).unwrap();
|
||||||
|
if e != 2 {
|
||||||
|
bet.write_all(b"j\n").unwrap();
|
||||||
|
}
|
||||||
|
alef.write_all(if f == 0 { b"\n" } else { b"l\n" }).unwrap();
|
||||||
|
if f != 2 {
|
||||||
|
bet.write_all(b"l\n").unwrap();
|
||||||
|
}
|
||||||
|
// This test diff is intentionally reversed.
|
||||||
|
// We want it to turn the alef into bet.
|
||||||
|
let diff = diff_w(&alef, &bet, "target/alef_").unwrap();
|
||||||
|
File::create("target/ab_.ed")
|
||||||
|
.unwrap()
|
||||||
|
.write_all(&diff)
|
||||||
|
.unwrap();
|
||||||
|
let mut fa = File::create("target/alef_").unwrap();
|
||||||
|
fa.write_all(&alef[..]).unwrap();
|
||||||
|
let mut fb = File::create(&format!("{target}/bet_")).unwrap();
|
||||||
|
fb.write_all(&bet[..]).unwrap();
|
||||||
|
let _ = fa;
|
||||||
|
let _ = fb;
|
||||||
|
let output = Command::new("ed")
|
||||||
|
.arg("target/alef_")
|
||||||
|
.stdin(File::open("target/ab_.ed").unwrap())
|
||||||
|
.output()
|
||||||
|
.unwrap();
|
||||||
|
assert!(output.status.success(), "{output:?}");
|
||||||
|
//println!("{}", String::from_utf8_lossy(&output.stdout));
|
||||||
|
//println!("{}", String::from_utf8_lossy(&output.stderr));
|
||||||
|
let alef = fs::read("target/alef_").unwrap();
|
||||||
|
assert_eq!(alef, bet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_permutations_reverse() {
|
||||||
|
let target = "target/ed-diff/";
|
||||||
|
// test all possible six-line files.
|
||||||
|
let _ = std::fs::create_dir(target);
|
||||||
|
for &a in &[0, 1, 2] {
|
||||||
|
for &b in &[0, 1, 2] {
|
||||||
|
for &c in &[0, 1, 2] {
|
||||||
|
for &d in &[0, 1, 2] {
|
||||||
|
for &e in &[0, 1, 2] {
|
||||||
|
for &f in &[0, 1, 2] {
|
||||||
|
use std::fs::{self, File};
|
||||||
|
use std::io::Write;
|
||||||
|
use std::process::Command;
|
||||||
|
let mut alef = Vec::new();
|
||||||
|
let mut bet = Vec::new();
|
||||||
|
alef.write_all(if a == 0 { b"a\n" } else { b"f\n" })
|
||||||
|
.unwrap();
|
||||||
|
if a != 2 {
|
||||||
|
bet.write_all(b"a\n").unwrap();
|
||||||
|
}
|
||||||
|
alef.write_all(if b == 0 { b"b\n" } else { b"e\n" })
|
||||||
|
.unwrap();
|
||||||
|
if b != 2 {
|
||||||
|
bet.write_all(b"b\n").unwrap();
|
||||||
|
}
|
||||||
|
alef.write_all(if c == 0 { b"c\n" } else { b"d\n" })
|
||||||
|
.unwrap();
|
||||||
|
if c != 2 {
|
||||||
|
bet.write_all(b"c\n").unwrap();
|
||||||
|
}
|
||||||
|
alef.write_all(if d == 0 { b"d\n" } else { b"c\n" })
|
||||||
|
.unwrap();
|
||||||
|
if d != 2 {
|
||||||
|
bet.write_all(b"d\n").unwrap();
|
||||||
|
}
|
||||||
|
alef.write_all(if e == 0 { b"e\n" } else { b"b\n" })
|
||||||
|
.unwrap();
|
||||||
|
if e != 2 {
|
||||||
|
bet.write_all(b"e\n").unwrap();
|
||||||
|
}
|
||||||
|
alef.write_all(if f == 0 { b"f\n" } else { b"a\n" })
|
||||||
|
.unwrap();
|
||||||
|
if f != 2 {
|
||||||
|
bet.write_all(b"f\n").unwrap();
|
||||||
|
}
|
||||||
|
// This test diff is intentionally reversed.
|
||||||
|
// We want it to turn the alef into bet.
|
||||||
|
let diff = diff_w(&alef, &bet, &format!("{target}/alefr")).unwrap();
|
||||||
|
File::create("target/abr.ed")
|
||||||
|
.unwrap()
|
||||||
|
.write_all(&diff)
|
||||||
|
.unwrap();
|
||||||
|
let mut fa = File::create(&format!("{target}/alefr")).unwrap();
|
||||||
|
fa.write_all(&alef[..]).unwrap();
|
||||||
|
let mut fb = File::create(&format!("{target}/betr")).unwrap();
|
||||||
|
fb.write_all(&bet[..]).unwrap();
|
||||||
|
let _ = fa;
|
||||||
|
let _ = fb;
|
||||||
|
let output = Command::new("ed")
|
||||||
|
.arg(&format!("{target}/alefr"))
|
||||||
|
.stdin(File::open("target/abr.ed").unwrap())
|
||||||
|
.output()
|
||||||
|
.unwrap();
|
||||||
|
assert!(output.status.success(), "{output:?}");
|
||||||
|
//println!("{}", String::from_utf8_lossy(&output.stdout));
|
||||||
|
//println!("{}", String::from_utf8_lossy(&output.stderr));
|
||||||
|
let alef = fs::read(&format!("{target}/alefr")).unwrap();
|
||||||
|
assert_eq!(alef, bet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_stop_early() {
|
||||||
|
let from = ["a", "b", "c", ""].join("\n");
|
||||||
|
let to = ["a", "d", "c", ""].join("\n");
|
||||||
|
|
||||||
|
let diff_full = diff(from.as_bytes(), to.as_bytes(), &Params::default()).unwrap();
|
||||||
|
let expected_full = ["2c", "d", ".", ""].join("\n");
|
||||||
|
assert_eq!(diff_full, expected_full.as_bytes());
|
||||||
|
|
||||||
|
let diff_brief = diff(
|
||||||
|
from.as_bytes(),
|
||||||
|
to.as_bytes(),
|
||||||
|
&Params {
|
||||||
|
brief: true,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let expected_brief = "\0".as_bytes();
|
||||||
|
assert_eq!(diff_brief, expected_brief);
|
||||||
|
|
||||||
|
let nodiff_full = diff(from.as_bytes(), from.as_bytes(), &Params::default()).unwrap();
|
||||||
|
assert!(nodiff_full.is_empty());
|
||||||
|
|
||||||
|
let nodiff_brief = diff(
|
||||||
|
from.as_bytes(),
|
||||||
|
from.as_bytes(),
|
||||||
|
&Params {
|
||||||
|
brief: true,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert!(nodiff_brief.is_empty());
|
||||||
|
}
|
||||||
|
}
|
||||||
+11
-781
File diff suppressed because it is too large
Load Diff
+69
-39
@@ -1,55 +1,85 @@
|
|||||||
// Sample program. Do not use.
|
// This file is part of the uutils diffutils package.
|
||||||
|
//
|
||||||
|
// For the full copyright and license information, please view the LICENSE-*
|
||||||
|
// files that was distributed with this source code.
|
||||||
|
|
||||||
|
use crate::params::{parse_params, Format};
|
||||||
use std::env;
|
use std::env;
|
||||||
|
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::io::{self, Write};
|
use std::io::{self, Write};
|
||||||
use std::process;
|
use std::process::{exit, ExitCode};
|
||||||
fn main() {
|
|
||||||
let mut o = env::args_os();
|
mod context_diff;
|
||||||
// parse CLI
|
mod ed_diff;
|
||||||
let exe = match o.next() {
|
mod normal_diff;
|
||||||
Some(from) => from,
|
mod params;
|
||||||
None => {
|
mod unified_diff;
|
||||||
eprintln!("Usage: [exe] [from] [to]");
|
mod utils;
|
||||||
process::exit(1);
|
|
||||||
}
|
// Exit codes are documented at
|
||||||
};
|
// https://www.gnu.org/software/diffutils/manual/html_node/Invoking-diff.html.
|
||||||
let from = match o.next() {
|
// An exit status of 0 means no differences were found,
|
||||||
Some(from) => from,
|
// 1 means some differences were found,
|
||||||
None => {
|
// and 2 means trouble.
|
||||||
eprintln!("Usage: {} [from] [to]", exe.to_string_lossy());
|
fn main() -> ExitCode {
|
||||||
process::exit(1);
|
let opts = env::args_os();
|
||||||
}
|
let params = parse_params(opts).unwrap_or_else(|error| {
|
||||||
};
|
eprintln!("{error}");
|
||||||
let to = match o.next() {
|
exit(2);
|
||||||
Some(from) => from,
|
});
|
||||||
None => {
|
// if from and to are the same file, no need to perform any comparison
|
||||||
eprintln!("Usage: {} [from] [to]", exe.to_string_lossy());
|
let maybe_report_identical_files = || {
|
||||||
process::exit(1);
|
if params.report_identical_files {
|
||||||
|
println!(
|
||||||
|
"Files {} and {} are identical",
|
||||||
|
params.from.to_string_lossy(),
|
||||||
|
params.to.to_string_lossy(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
if same_file::is_same_file(¶ms.from, ¶ms.to).unwrap_or(false) {
|
||||||
|
maybe_report_identical_files();
|
||||||
|
return ExitCode::SUCCESS;
|
||||||
|
}
|
||||||
// read files
|
// read files
|
||||||
let from_content = match fs::read(&from) {
|
let from_content = match fs::read(¶ms.from) {
|
||||||
Ok(from_content) => from_content,
|
Ok(from_content) => from_content,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("Failed to read from-file: {}", e);
|
eprintln!("Failed to read from-file: {e}");
|
||||||
process::exit(2);
|
return ExitCode::from(2);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let to_content = match fs::read(&to) {
|
let to_content = match fs::read(¶ms.to) {
|
||||||
Ok(to_content) => to_content,
|
Ok(to_content) => to_content,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("Failed to read to-file: {}", e);
|
eprintln!("Failed to read to-file: {e}");
|
||||||
process::exit(2);
|
return ExitCode::from(2);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// run diff
|
// run diff
|
||||||
io::stdout()
|
let result: Vec<u8> = match params.format {
|
||||||
.write_all(&unified_diff::diff(
|
Format::Normal => normal_diff::diff(&from_content, &to_content, ¶ms),
|
||||||
&from_content,
|
Format::Unified => unified_diff::diff(&from_content, &to_content, ¶ms),
|
||||||
&from.to_string_lossy(),
|
Format::Context => context_diff::diff(&from_content, &to_content, ¶ms),
|
||||||
&to_content,
|
Format::Ed => ed_diff::diff(&from_content, &to_content, ¶ms).unwrap_or_else(|error| {
|
||||||
&to.to_string_lossy(),
|
eprintln!("{error}");
|
||||||
1,
|
exit(2);
|
||||||
))
|
}),
|
||||||
.unwrap();
|
};
|
||||||
|
if params.brief && !result.is_empty() {
|
||||||
|
println!(
|
||||||
|
"Files {} and {} differ",
|
||||||
|
params.from.to_string_lossy(),
|
||||||
|
params.to.to_string_lossy()
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
io::stdout().write_all(&result).unwrap();
|
||||||
|
}
|
||||||
|
if result.is_empty() {
|
||||||
|
maybe_report_identical_files();
|
||||||
|
ExitCode::SUCCESS
|
||||||
|
} else {
|
||||||
|
ExitCode::from(1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
+505
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+118
@@ -0,0 +1,118 @@
|
|||||||
|
// This file is part of the uutils diffutils package.
|
||||||
|
//
|
||||||
|
// For the full copyright and license information, please view the LICENSE-*
|
||||||
|
// files that was distributed with this source code.
|
||||||
|
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
use unicode_width::UnicodeWidthStr;
|
||||||
|
|
||||||
|
/// Replace tabs by spaces in the input line.
|
||||||
|
/// Correctly handle multi-bytes characters.
|
||||||
|
/// This assumes that line does not contain any line breaks (if it does, the result is undefined).
|
||||||
|
#[must_use]
|
||||||
|
pub fn do_expand_tabs(line: &[u8], tabsize: usize) -> Vec<u8> {
|
||||||
|
let tab = b'\t';
|
||||||
|
let ntabs = line.iter().filter(|c| **c == tab).count();
|
||||||
|
if ntabs == 0 {
|
||||||
|
return line.to_vec();
|
||||||
|
}
|
||||||
|
let mut result = Vec::with_capacity(line.len() + ntabs * (tabsize - 1));
|
||||||
|
let mut offset = 0;
|
||||||
|
|
||||||
|
let mut iter = line.split(|c| *c == tab).peekable();
|
||||||
|
while let Some(chunk) = iter.next() {
|
||||||
|
match String::from_utf8(chunk.to_vec()) {
|
||||||
|
Ok(s) => offset += UnicodeWidthStr::width(s.as_str()),
|
||||||
|
Err(_) => offset += chunk.len(),
|
||||||
|
}
|
||||||
|
result.extend_from_slice(chunk);
|
||||||
|
if iter.peek().is_some() {
|
||||||
|
result.resize(result.len() + tabsize - offset % tabsize, b' ');
|
||||||
|
offset = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write a single line to an output stream, expanding tabs to space if necessary.
|
||||||
|
/// This assumes that line does not contain any line breaks
|
||||||
|
/// (if it does and tabs are to be expanded to spaces, the result is undefined).
|
||||||
|
pub fn do_write_line(
|
||||||
|
output: &mut Vec<u8>,
|
||||||
|
line: &[u8],
|
||||||
|
expand_tabs: bool,
|
||||||
|
tabsize: usize,
|
||||||
|
) -> std::io::Result<()> {
|
||||||
|
if expand_tabs {
|
||||||
|
output.write_all(do_expand_tabs(line, tabsize).as_slice())
|
||||||
|
} else {
|
||||||
|
output.write_all(line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
mod expand_tabs {
|
||||||
|
use super::*;
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
|
fn assert_tab_expansion(line: &str, tabsize: usize, expected: &str) {
|
||||||
|
assert_eq!(
|
||||||
|
do_expand_tabs(line.as_bytes(), tabsize),
|
||||||
|
expected.as_bytes()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn basics() {
|
||||||
|
assert_tab_expansion("foo barr baz", 8, "foo barr baz");
|
||||||
|
assert_tab_expansion("foo\tbarr\tbaz", 8, "foo barr baz");
|
||||||
|
assert_tab_expansion("foo\tbarr\tbaz", 5, "foo barr baz");
|
||||||
|
assert_tab_expansion("foo\tbarr\tbaz", 2, "foo barr baz");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn multibyte_chars() {
|
||||||
|
assert_tab_expansion("foo\tépée\tbaz", 8, "foo épée baz");
|
||||||
|
assert_tab_expansion("foo\t😉\tbaz", 5, "foo 😉 baz");
|
||||||
|
|
||||||
|
// Note: The Woman Scientist emoji (👩🔬) is a ZWJ sequence combining
|
||||||
|
// the Woman emoji (👩) and the Microscope emoji (🔬). On supported platforms
|
||||||
|
// it is displayed as a single emoji and should have a print size of 2 columns,
|
||||||
|
// but terminal emulators tend to not support this, and display the two emojis
|
||||||
|
// side by side, thus accounting for a print size of 4 columns.
|
||||||
|
assert_tab_expansion("foo\t👩🔬\tbaz", 6, "foo 👩🔬 baz");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn invalid_utf8() {
|
||||||
|
// [240, 240, 152, 137] is an invalid UTF-8 sequence, so it is handled as 4 bytes
|
||||||
|
assert_eq!(
|
||||||
|
do_expand_tabs(&[240, 240, 152, 137, 9, 102, 111, 111], 8),
|
||||||
|
&[240, 240, 152, 137, 32, 32, 32, 32, 102, 111, 111]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod write_line {
|
||||||
|
use super::*;
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
|
fn assert_line_written(line: &str, expand_tabs: bool, tabsize: usize, expected: &str) {
|
||||||
|
let mut output: Vec<u8> = Vec::new();
|
||||||
|
assert!(do_write_line(&mut output, line.as_bytes(), expand_tabs, tabsize).is_ok());
|
||||||
|
assert_eq!(output, expected.as_bytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn basics() {
|
||||||
|
assert_line_written("foo bar baz", false, 8, "foo bar baz");
|
||||||
|
assert_line_written("foo bar\tbaz", false, 8, "foo bar\tbaz");
|
||||||
|
assert_line_written("foo bar\tbaz", true, 8, "foo bar baz");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,205 @@
|
|||||||
|
// This file is part of the uutils diffutils package.
|
||||||
|
//
|
||||||
|
// For the full copyright and license information, please view the LICENSE-*
|
||||||
|
// files that was distributed with this source code.
|
||||||
|
|
||||||
|
use assert_cmd::cmd::Command;
|
||||||
|
use predicates::prelude::*;
|
||||||
|
use std::io::Write;
|
||||||
|
use tempfile::NamedTempFile;
|
||||||
|
|
||||||
|
// Integration tests for the diffutils command
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unknown_param() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let mut cmd = Command::cargo_bin("diffutils")?;
|
||||||
|
cmd.arg("--foobar");
|
||||||
|
cmd.assert()
|
||||||
|
.code(predicate::eq(2))
|
||||||
|
.failure()
|
||||||
|
.stderr(predicate::str::starts_with("Usage: "));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cannot_read_from_file() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let mut cmd = Command::cargo_bin("diffutils")?;
|
||||||
|
cmd.arg("foo.txt").arg("bar.txt");
|
||||||
|
cmd.assert()
|
||||||
|
.code(predicate::eq(2))
|
||||||
|
.failure()
|
||||||
|
.stderr(predicate::str::starts_with("Failed to read from-file"));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cannot_read_to_file() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let file = NamedTempFile::new()?;
|
||||||
|
let mut cmd = Command::cargo_bin("diffutils")?;
|
||||||
|
cmd.arg(file.path()).arg("bar.txt");
|
||||||
|
cmd.assert()
|
||||||
|
.code(predicate::eq(2))
|
||||||
|
.failure()
|
||||||
|
.stderr(predicate::str::starts_with("Failed to read to-file"));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_differences() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let file = NamedTempFile::new()?;
|
||||||
|
for option in ["", "-u", "-c", "-e"] {
|
||||||
|
let mut cmd = Command::cargo_bin("diffutils")?;
|
||||||
|
if !option.is_empty() {
|
||||||
|
cmd.arg(option);
|
||||||
|
}
|
||||||
|
cmd.arg(file.path()).arg(file.path());
|
||||||
|
cmd.assert()
|
||||||
|
.code(predicate::eq(0))
|
||||||
|
.success()
|
||||||
|
.stdout(predicate::str::is_empty());
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_differences_report_identical_files() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
// same file
|
||||||
|
let mut file1 = NamedTempFile::new()?;
|
||||||
|
file1.write_all("foo\n".as_bytes())?;
|
||||||
|
for option in ["", "-u", "-c", "-e"] {
|
||||||
|
let mut cmd = Command::cargo_bin("diffutils")?;
|
||||||
|
if !option.is_empty() {
|
||||||
|
cmd.arg(option);
|
||||||
|
}
|
||||||
|
cmd.arg("-s").arg(file1.path()).arg(file1.path());
|
||||||
|
cmd.assert()
|
||||||
|
.code(predicate::eq(0))
|
||||||
|
.success()
|
||||||
|
.stdout(predicate::eq(format!(
|
||||||
|
"Files {} and {} are identical\n",
|
||||||
|
file1.path().to_string_lossy(),
|
||||||
|
file1.path().to_string_lossy(),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
// two files with the same content
|
||||||
|
let mut file2 = NamedTempFile::new()?;
|
||||||
|
file2.write_all("foo\n".as_bytes())?;
|
||||||
|
for option in ["", "-u", "-c", "-e"] {
|
||||||
|
let mut cmd = Command::cargo_bin("diffutils")?;
|
||||||
|
if !option.is_empty() {
|
||||||
|
cmd.arg(option);
|
||||||
|
}
|
||||||
|
cmd.arg("-s").arg(file1.path()).arg(file2.path());
|
||||||
|
cmd.assert()
|
||||||
|
.code(predicate::eq(0))
|
||||||
|
.success()
|
||||||
|
.stdout(predicate::eq(format!(
|
||||||
|
"Files {} and {} are identical\n",
|
||||||
|
file1.path().to_string_lossy(),
|
||||||
|
file2.path().to_string_lossy(),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn differences() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let mut file1 = NamedTempFile::new()?;
|
||||||
|
file1.write_all("foo\n".as_bytes())?;
|
||||||
|
let mut file2 = NamedTempFile::new()?;
|
||||||
|
file2.write_all("bar\n".as_bytes())?;
|
||||||
|
for option in ["", "-u", "-c", "-e"] {
|
||||||
|
let mut cmd = Command::cargo_bin("diffutils")?;
|
||||||
|
if !option.is_empty() {
|
||||||
|
cmd.arg(option);
|
||||||
|
}
|
||||||
|
cmd.arg(file1.path()).arg(file2.path());
|
||||||
|
cmd.assert()
|
||||||
|
.code(predicate::eq(1))
|
||||||
|
.failure()
|
||||||
|
.stdout(predicate::str::is_empty().not());
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn differences_brief() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let mut file1 = NamedTempFile::new()?;
|
||||||
|
file1.write_all("foo\n".as_bytes())?;
|
||||||
|
let mut file2 = NamedTempFile::new()?;
|
||||||
|
file2.write_all("bar\n".as_bytes())?;
|
||||||
|
for option in ["", "-u", "-c", "-e"] {
|
||||||
|
let mut cmd = Command::cargo_bin("diffutils")?;
|
||||||
|
if !option.is_empty() {
|
||||||
|
cmd.arg(option);
|
||||||
|
}
|
||||||
|
cmd.arg("-q").arg(file1.path()).arg(file2.path());
|
||||||
|
cmd.assert()
|
||||||
|
.code(predicate::eq(1))
|
||||||
|
.failure()
|
||||||
|
.stdout(predicate::eq(format!(
|
||||||
|
"Files {} and {} differ\n",
|
||||||
|
file1.path().to_string_lossy(),
|
||||||
|
file2.path().to_string_lossy()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn missing_newline() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let mut file1 = NamedTempFile::new()?;
|
||||||
|
file1.write_all("foo".as_bytes())?;
|
||||||
|
let mut file2 = NamedTempFile::new()?;
|
||||||
|
file2.write_all("bar".as_bytes())?;
|
||||||
|
let mut cmd = Command::cargo_bin("diffutils")?;
|
||||||
|
cmd.arg("-e").arg(file1.path()).arg(file2.path());
|
||||||
|
cmd.assert()
|
||||||
|
.code(predicate::eq(2))
|
||||||
|
.failure()
|
||||||
|
.stderr(predicate::str::starts_with("No newline at end of file"));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn read_from_stdin() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let mut file1 = NamedTempFile::new()?;
|
||||||
|
file1.write_all("foo\n".as_bytes())?;
|
||||||
|
let mut file2 = NamedTempFile::new()?;
|
||||||
|
file2.write_all("bar\n".as_bytes())?;
|
||||||
|
|
||||||
|
let mut cmd = Command::cargo_bin("diffutils")?;
|
||||||
|
cmd.arg("-u")
|
||||||
|
.arg(file1.path())
|
||||||
|
.arg("-")
|
||||||
|
.write_stdin("bar\n");
|
||||||
|
cmd.assert()
|
||||||
|
.code(predicate::eq(1))
|
||||||
|
.failure()
|
||||||
|
.stdout(predicate::eq(format!(
|
||||||
|
"--- {}\t\n+++ /dev/stdin\t\n@@ -1 +1 @@\n-foo\n+bar\n",
|
||||||
|
file1.path().to_string_lossy()
|
||||||
|
)));
|
||||||
|
|
||||||
|
let mut cmd = Command::cargo_bin("diffutils")?;
|
||||||
|
cmd.arg("-u")
|
||||||
|
.arg("-")
|
||||||
|
.arg(file2.path())
|
||||||
|
.write_stdin("foo\n");
|
||||||
|
cmd.assert()
|
||||||
|
.code(predicate::eq(1))
|
||||||
|
.failure()
|
||||||
|
.stdout(predicate::eq(format!(
|
||||||
|
"--- /dev/stdin\t\n+++ {}\t\n@@ -1 +1 @@\n-foo\n+bar\n",
|
||||||
|
file2.path().to_string_lossy()
|
||||||
|
)));
|
||||||
|
|
||||||
|
let mut cmd = Command::cargo_bin("diffutils")?;
|
||||||
|
cmd.arg("-u").arg("-").arg("-").write_stdin("foo\n");
|
||||||
|
cmd.assert()
|
||||||
|
.code(predicate::eq(0))
|
||||||
|
.success()
|
||||||
|
.stdout(predicate::str::is_empty());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user