Compare commits
219
Commits
latest-commit
...
0.2.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2ce633cb8d | ||
|
|
91465e566b | ||
|
|
d842aa305e | ||
|
|
d28a1f8e73 | ||
|
|
af5f8afdcb | ||
|
|
8f3f126816 | ||
|
|
c771318a7a | ||
|
|
36a1cf4954 | ||
|
|
13bb82ba13 | ||
|
|
cb9eb1da42 | ||
|
|
b101462d1b | ||
|
|
9f2e1a1ad4 | ||
|
|
2b092d53b0 | ||
|
|
550d545d72 | ||
|
|
a5d219764f | ||
|
|
397ab86302 | ||
|
|
2288789f33 | ||
|
|
84ae1b07a7 | ||
|
|
f32296fc22 | ||
|
|
7239fb0e08 | ||
|
|
c7e1f78486 | ||
|
|
fb907703a0 | ||
|
|
3d52077d7f | ||
|
|
e9792a8ef9 | ||
|
|
c732b60cfc | ||
|
|
c1e5d8ea08 | ||
|
|
6887201cce | ||
|
|
75d856e4c2 | ||
|
|
d7a2106258 | ||
|
|
a726ac6959 | ||
|
|
49590e63be | ||
|
|
99a9957e89 | ||
|
|
c521271ca8 | ||
|
|
7830c20bc1 | ||
|
|
b37e23fa98 | ||
|
|
07eada8531 | ||
|
|
15def35a2a | ||
|
|
30607c4ddc | ||
|
|
01c8412327 | ||
|
|
f3a79c5847 | ||
|
|
a9e5fe778c | ||
|
|
02d0239447 | ||
|
|
8b5dea703a | ||
|
|
34dfbc2ef1 | ||
|
|
8cb7090bbc | ||
|
|
2a5ab5a9a2 | ||
|
|
b14272c1d7 | ||
|
|
cd3b10db48 | ||
|
|
48c2f56358 | ||
|
|
dcd2df848a | ||
|
|
9efab796be | ||
|
|
b74925283f | ||
|
|
8baab710c3 | ||
|
|
5ca2636ce3 | ||
|
|
8d28215c1b | ||
|
|
88d188aa03 | ||
|
|
5e5281be03 | ||
|
|
4eda475d5c | ||
|
|
9b388fca34 | ||
|
|
b90c599fac | ||
|
|
d9cd53883c | ||
|
|
3eb27c7831 | ||
|
|
3c7488ba1f | ||
|
|
5122acc896 | ||
|
|
a9a332af1c | ||
|
|
b765a4b0c1 | ||
|
|
508d74fe4a | ||
|
|
c45b2f5516 | ||
|
|
3b846498f4 | ||
|
|
238648ddbf | ||
|
|
d37963afcb | ||
|
|
9ebf4e2ba2 | ||
|
|
9d0d86a8f1 | ||
|
|
7c6cbf9dba | ||
|
|
a65af95b5c | ||
|
|
47cebdd562 | ||
|
|
f6e69e734b | ||
|
|
2dbeaea2e1 | ||
|
|
5cef1c4327 | ||
|
|
4171a7fe61 | ||
|
|
142e70bd13 | ||
|
|
f083fa0634 | ||
|
|
ff28e7d83d | ||
|
|
6c00065099 | ||
|
|
cd2e3cba30 | ||
|
|
7f00ab2f16 | ||
|
|
249a4f4a26 | ||
|
|
5fcb15dba5 | ||
|
|
f783eb975c | ||
|
|
5856b37cea | ||
|
|
84c32f8ef0 | ||
|
|
433635b583 | ||
|
|
c5cbd62f9b | ||
|
|
b94b148638 | ||
|
|
a6c028b782 | ||
|
|
7e7abc9407 | ||
|
|
fef7a40433 | ||
|
|
60243b4fa7 | ||
|
|
b7072cb4a5 | ||
|
|
6215664b2e | ||
|
|
ffe4130654 | ||
|
|
e435b9be6c | ||
|
|
5d9f08af1c | ||
|
|
14b156995e | ||
|
|
187ae761b1 | ||
|
|
b96e0e78d8 | ||
|
|
442c2ed0b9 | ||
|
|
d7a713e3c8 | ||
|
|
0cf23a8e34 | ||
|
|
2724f4ad75 | ||
|
|
ebb321e90e | ||
|
|
17a2fece6f | ||
|
|
d389e51925 | ||
|
|
a2da4c1a3b | ||
|
|
7b204c022b | ||
|
|
af30aa40a4 | ||
|
|
4cd9858bfd | ||
|
|
b4c567e43e | ||
|
|
fc2db1e335 | ||
|
|
35bc81a5cf | ||
|
|
59d24ac8dd | ||
|
|
37df236504 | ||
|
|
2e76232e05 | ||
|
|
b72279afb3 | ||
|
|
08d3454b8c | ||
|
|
17f36e91c1 | ||
|
|
5e2eb59959 | ||
|
|
e3ff6cc268 | ||
|
|
e012c4b5c0 | ||
|
|
59551c580b | ||
|
|
deb64e0ccd | ||
|
|
68b5883b0e | ||
|
|
1b747a66a8 | ||
|
|
7fade42bb5 | ||
|
|
f334e1000c | ||
|
|
ed6869944f | ||
|
|
ad01bedff8 | ||
|
|
34651493c1 | ||
|
|
bf7bed170a | ||
|
|
1469cccfde | ||
|
|
5e9b821d80 | ||
|
|
27da0276ec | ||
|
|
2836cb7257 | ||
|
|
8c09b0f5a5 | ||
|
|
5f85e267db | ||
|
|
1535cfdd9b | ||
|
|
8772c166d9 | ||
|
|
6b6067075b | ||
|
|
dc9be6018c | ||
|
|
b295d213e8 | ||
|
|
d3ca9d27b4 | ||
|
|
16f11456b3 | ||
|
|
81d98b9049 | ||
|
|
25efbc0ab7 | ||
|
|
ad1f3d22dc | ||
|
|
3f3dd1270e | ||
|
|
af7131fdb2 | ||
|
|
97e70434b9 | ||
|
|
19363fa810 | ||
|
|
56e6ef5d2f | ||
|
|
fadc36512d | ||
|
|
61152af62b | ||
|
|
7f16a008c9 | ||
|
|
a6d85d8e42 | ||
|
|
dc91d3709c | ||
|
|
8a3f63db27 | ||
|
|
1bb2f89e8d | ||
|
|
b7df132ba1 | ||
|
|
9f6f052c27 | ||
|
|
569ec711ac | ||
|
|
e075bff043 | ||
|
|
628d320e5b | ||
|
|
d102d0be73 | ||
|
|
01b190c59a | ||
|
|
5ecd6afedb | ||
|
|
81e71bcb49 | ||
|
|
b169b4dfc2 | ||
|
|
049e149ea5 | ||
|
|
91affcf90b | ||
|
|
99c8574fc5 | ||
|
|
b5b4cefb77 | ||
|
|
fee1ff33ae | ||
|
|
22abe5ea6c | ||
|
|
c0c85e65fa | ||
|
|
ab6f9d9810 | ||
|
|
1d04c82eb8 | ||
|
|
cdae35308d | ||
|
|
b926499ed4 | ||
|
|
4be480767c | ||
|
|
aeac7d1e63 | ||
|
|
3c7b8ff4ba | ||
|
|
a177e97cfb | ||
|
|
361c45e6c3 | ||
|
|
1c57aaa8e2 | ||
|
|
1bb2609f16 | ||
|
|
e3684f0029 | ||
|
|
f27060898a | ||
|
|
1dc91b0082 | ||
|
|
c3bbd572e9 | ||
|
|
8d7559b52d | ||
|
|
995e32a70d | ||
|
|
3f26fc2936 | ||
|
|
907d01b847 | ||
|
|
3504109373 | ||
|
|
837f2cd2ea | ||
|
|
0a09d32b00 | ||
|
|
da2715dcb5 | ||
|
|
40925c8b4f | ||
|
|
2eeaa80c3b | ||
|
|
a6207cbebc | ||
|
|
98f7338960 | ||
|
|
003b579967 | ||
|
|
d497e881af | ||
|
|
98c4dab5dd | ||
|
|
8e8c2bf9f7 | ||
|
|
3cf5ea5beb | ||
|
|
1617d32d5d | ||
|
|
b3350a21c4 | ||
|
|
8c46bec97f |
No files matched your search
@@ -18,7 +18,7 @@ jobs:
|
||||
github.event.workflow_run.event == 'pull_request'
|
||||
steps:
|
||||
- name: 'Download artifact'
|
||||
uses: actions/github-script@v8
|
||||
uses: actions/github-script@v9
|
||||
with:
|
||||
script: |
|
||||
// List all artifacts from GnuTests
|
||||
@@ -32,12 +32,12 @@ jobs:
|
||||
var matchArtifact = artifacts.data.artifacts.filter((artifact) => {
|
||||
return artifact.name == "comment"
|
||||
})[0];
|
||||
|
||||
|
||||
if (!matchArtifact) {
|
||||
console.log('No comment artifact found');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
var download = await github.rest.actions.downloadArtifact({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
@@ -49,12 +49,12 @@ jobs:
|
||||
- run: unzip comment.zip || echo "Failed to unzip comment artifact"
|
||||
|
||||
- name: 'Comment on PR'
|
||||
uses: actions/github-script@v8
|
||||
uses: actions/github-script@v9
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
var fs = require('fs');
|
||||
|
||||
|
||||
// Check if files exist
|
||||
if (!fs.existsSync('./NR')) {
|
||||
console.log('No NR file found, skipping comment');
|
||||
@@ -64,10 +64,10 @@ jobs:
|
||||
console.log('No result.txt file found, skipping comment');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
var issue_number = Number(fs.readFileSync('./NR'));
|
||||
var content = fs.readFileSync('./result.txt');
|
||||
|
||||
|
||||
if (content.toString().trim().length > 7) { // 7 because we have backquote + \n
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
@@ -77,4 +77,4 @@ jobs:
|
||||
});
|
||||
} else {
|
||||
console.log('Comment content too short, skipping');
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
steps:
|
||||
#### Get the code, setup cache
|
||||
- name: Checkout code (sed)
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
path: 'sed'
|
||||
persist-credentials: false
|
||||
@@ -40,7 +40,7 @@ jobs:
|
||||
with:
|
||||
workspaces: "./sed -> target"
|
||||
- name: Checkout code (GNU sed testsuite)
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
repository: 'mirror/sed'
|
||||
path: 'gnu.sed'
|
||||
@@ -56,16 +56,18 @@ jobs:
|
||||
## Build Rust sed binary
|
||||
cd 'sed'
|
||||
cargo build --config=profile.release.strip=true --profile=release #-fast
|
||||
zstd -19 target/release/sed -o ../sed-x86_64-unknown-linux-gnu.zst
|
||||
tar -C target/release -cf - sed | zstd -19 -o ../sed-x86_64-unknown-linux-gnu.tar.zst
|
||||
- name: Publish latest commit
|
||||
uses: softprops/action-gh-release@v2
|
||||
uses: softprops/action-gh-release@v3
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
with:
|
||||
tag_name: latest-commit
|
||||
body: |
|
||||
commit: ${{ github.sha }}
|
||||
draft: false
|
||||
prerelease: true
|
||||
files: |
|
||||
sed-x86_64-unknown-linux-gnu.zst
|
||||
sed-x86_64-unknown-linux-gnu.tar.zst
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -75,8 +77,8 @@ jobs:
|
||||
run: |
|
||||
## Run GNU sed testsuite using our script
|
||||
cd 'sed'
|
||||
# Set GNU testsuite directory
|
||||
export GNU_TESTSUITE_DIR="../gnu.sed/testsuite"
|
||||
# Set GNU sed source directory (testsuite is at gnu.sed/testsuite/)
|
||||
export GNU_SED_DIR="../gnu.sed"
|
||||
# Run tests with JSON output
|
||||
./util/run-gnu-testsuite.sh --json-output "${{ env.TEST_FULL_SUMMARY_FILE }}" || true
|
||||
|
||||
@@ -91,7 +93,7 @@ jobs:
|
||||
find . -name "*json*" -type f || echo "No JSON files found"
|
||||
|
||||
- name: Upload full json results
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: sed-gnu-full-result
|
||||
path: sed/${{ env.TEST_FULL_SUMMARY_FILE }}
|
||||
@@ -99,7 +101,7 @@ jobs:
|
||||
|
||||
- name: Upload test logs
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: test-logs
|
||||
path: |
|
||||
@@ -126,13 +128,13 @@ jobs:
|
||||
outputs TEST_SUMMARY_FILE
|
||||
|
||||
- name: Checkout code (sed)
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
path: 'sed'
|
||||
persist-credentials: false
|
||||
|
||||
- name: Retrieve reference artifacts
|
||||
uses: dawidd6/action-download-artifact@v12
|
||||
uses: dawidd6/action-download-artifact@v21
|
||||
continue-on-error: true
|
||||
with:
|
||||
workflow: GnuTests.yml
|
||||
@@ -142,7 +144,7 @@ jobs:
|
||||
if_no_artifact_found: warn
|
||||
|
||||
- name: Download full json results
|
||||
uses: actions/download-artifact@v7
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: sed-gnu-full-result
|
||||
path: results
|
||||
@@ -203,13 +205,13 @@ jobs:
|
||||
outputs HASH TOTAL PASS FAIL SKIP
|
||||
|
||||
- name: Upload SHA1/ID of 'test-summary'
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: "${{ steps.summary.outputs.HASH }}"
|
||||
path: "${{ steps.vars.outputs.TEST_SUMMARY_FILE }}"
|
||||
|
||||
- name: Upload test results summary
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: test-summary
|
||||
path: "${{ steps.vars.outputs.TEST_SUMMARY_FILE }}"
|
||||
@@ -259,10 +261,10 @@ jobs:
|
||||
echo "ONLY_INTERMITTENT=true" >> $GITHUB_ENV
|
||||
echo "::notice ::No new test failures detected"
|
||||
fi
|
||||
|
||||
|
||||
- name: Upload comparison log (for GnuComment workflow)
|
||||
if: success() || failure()
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: comment
|
||||
path: reference/comment/
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
name: Security audit
|
||||
|
||||
# spell-checker:ignore (misc) rustsec
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 0 * * *"
|
||||
jobs:
|
||||
audit:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: rustsec/audit-check@v2
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -25,25 +25,20 @@ jobs:
|
||||
benchmark-target:
|
||||
- { package: sed }
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install system dependencies
|
||||
shell: bash
|
||||
run: |
|
||||
sudo apt-get -y update
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: Run sccache-cache
|
||||
uses: mozilla-actions/sccache-action@v0.0.9
|
||||
uses: mozilla-actions/sccache-action@v0.0.11
|
||||
|
||||
- name: Install cargo-codspeed
|
||||
shell: bash
|
||||
run: cargo install cargo-codspeed --locked
|
||||
- name: Install tools
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: cargo-codspeed
|
||||
|
||||
- name: Build benchmarks for ${{ matrix.benchmark-target.package }}
|
||||
shell: bash
|
||||
@@ -52,7 +47,7 @@ jobs:
|
||||
cargo codspeed build -p ${{ matrix.benchmark-target.package }}
|
||||
|
||||
- name: Run benchmarks for ${{ matrix.benchmark-target.package }}
|
||||
uses: CodSpeedHQ/action@v4
|
||||
uses: CodSpeedHQ/action@v5
|
||||
env:
|
||||
CODSPEED_LOG: debug
|
||||
with:
|
||||
|
||||
@@ -13,10 +13,31 @@ jobs:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macOS-latest, windows-latest]
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- run: cargo check
|
||||
|
||||
check_android:
|
||||
name: cargo check (Android ${{ matrix.target }})
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
target: [aarch64-linux-android, armv7-linux-androideabi]
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: ${{ matrix.target }}
|
||||
- uses: android-actions/setup-android@v4
|
||||
- name: Install Android NDK
|
||||
run: sdkmanager "ndk;29.0.14206865"
|
||||
- name: Install cargo-ndk
|
||||
run: cargo install cargo-ndk
|
||||
- name: Check Android target
|
||||
run: cargo ndk --platform 21 --target ${{ matrix.target }} check
|
||||
env:
|
||||
ANDROID_NDK_HOME: ${{ env.ANDROID_SDK_ROOT }}/ndk/29.0.14206865
|
||||
|
||||
test:
|
||||
name: cargo test
|
||||
runs-on: ${{ matrix.os }}
|
||||
@@ -28,7 +49,7 @@ jobs:
|
||||
run: |
|
||||
git config --global core.autocrlf false
|
||||
git config --global core.eol lf
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- run: cargo test --all
|
||||
|
||||
@@ -47,7 +68,7 @@ jobs:
|
||||
run: |
|
||||
git config --global core.autocrlf false
|
||||
git config --global core.eol lf
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
- name: Initialize workflow variables
|
||||
id: vars
|
||||
shell: bash
|
||||
@@ -109,7 +130,7 @@ jobs:
|
||||
ls -al
|
||||
echo "report=${COVERAGE_REPORT_FILE}" >> $GITHUB_OUTPUT
|
||||
- name: Upload coverage results (to Codecov.io)
|
||||
uses: codecov/codecov-action@v5
|
||||
uses: codecov/codecov-action@v7
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
files: ${{ steps.coverage.outputs.report }}
|
||||
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
name: cargo fmt --all -- --check
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- run: rustup component add rustfmt
|
||||
- run: cargo fmt --all -- --check
|
||||
@@ -44,14 +44,14 @@ jobs:
|
||||
- { os: macos-latest }
|
||||
- { os: windows-latest }
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
- uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: stable
|
||||
components: clippy
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: Run sccache-cache
|
||||
uses: mozilla-actions/sccache-action@v0.0.9
|
||||
uses: mozilla-actions/sccache-action@v0.0.11
|
||||
- name: Initialize workflow variables
|
||||
id: vars
|
||||
shell: bash
|
||||
|
||||
@@ -21,18 +21,20 @@ jobs:
|
||||
name: Build the fuzzers
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: dtolnay/rust-toolchain@nightly
|
||||
- name: Install `cargo-fuzz`
|
||||
run: cargo install cargo-fuzz
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: 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
|
||||
# https://github.com/rust-fuzz/cargo-fuzz/issues/398
|
||||
run: env RUSTC_BOOTSTRAP=1 cargo fuzz build --target $(rustc --print host-tuple)
|
||||
|
||||
fuzz-run:
|
||||
needs: fuzz-build
|
||||
@@ -47,18 +49,19 @@ jobs:
|
||||
- { name: fuzz_sed, should_pass: false }
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: dtolnay/rust-toolchain@nightly
|
||||
- name: Install `cargo-fuzz`
|
||||
run: cargo install cargo-fuzz
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: 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@v5
|
||||
uses: actions/cache/restore@v6
|
||||
with:
|
||||
key: corpus-cache-${{ matrix.test-target.name }}
|
||||
path: |
|
||||
@@ -70,7 +73,8 @@ jobs:
|
||||
run: |
|
||||
mkdir -p fuzz/stats
|
||||
STATS_FILE="fuzz/stats/${{ matrix.test-target.name }}.txt"
|
||||
cargo +nightly fuzz run ${{ matrix.test-target.name }} -- -max_total_time=${{ env.RUN_FOR }} -timeout=${{ env.RUN_FOR }} -detect_leaks=0 -print_final_stats=1 2>&1 | tee "$STATS_FILE"
|
||||
# https://github.com/rust-fuzz/cargo-fuzz/issues/398
|
||||
env RUSTC_BOOTSTRAP=1 cargo fuzz run --target $(rustc --print host-tuple) ${{ matrix.test-target.name }} -- -max_total_time=${{ env.RUN_FOR }} -timeout=${{ env.RUN_FOR }} -detect_leaks=0 -print_final_stats=1 2>&1 | tee "$STATS_FILE"
|
||||
|
||||
# Extract key stats from the output
|
||||
if grep -q "stat::number_of_executed_units" "$STATS_FILE"; then
|
||||
@@ -140,13 +144,13 @@ jobs:
|
||||
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
- name: Save Corpus Cache
|
||||
uses: actions/cache/save@v5
|
||||
uses: actions/cache/save@v6
|
||||
with:
|
||||
key: corpus-cache-${{ matrix.test-target.name }}
|
||||
path: |
|
||||
fuzz/corpus/${{ matrix.test-target.name }}
|
||||
- name: Upload Stats
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: fuzz-stats-${{ matrix.test-target.name }}
|
||||
path: |
|
||||
@@ -159,11 +163,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
if: always()
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Download all stats
|
||||
uses: actions/download-artifact@v7
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
path: fuzz/stats-artifacts
|
||||
pattern: fuzz-stats-*
|
||||
@@ -257,7 +261,7 @@ jobs:
|
||||
run: |
|
||||
cat fuzzing_summary.md
|
||||
- name: Upload Summary
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: fuzzing-summary
|
||||
path: fuzzing_summary.md
|
||||
|
||||
@@ -4,4 +4,4 @@
|
||||
# Add test names that are known to be flaky or environment-dependent
|
||||
# Example:
|
||||
# basic_substitution
|
||||
# line_address_test
|
||||
# line_address_test
|
||||
@@ -56,7 +56,7 @@ jobs:
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
@@ -66,7 +66,7 @@ jobs:
|
||||
shell: bash
|
||||
run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.30.3/cargo-dist-installer.sh | sh"
|
||||
- name: Cache dist
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: cargo-dist-cache
|
||||
path: ~/.cargo/bin/dist
|
||||
@@ -82,7 +82,7 @@ jobs:
|
||||
cat plan-dist-manifest.json
|
||||
echo "manifest=$(jq -c "." plan-dist-manifest.json)" >> "$GITHUB_OUTPUT"
|
||||
- name: "Upload dist-manifest.json"
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: artifacts-plan-dist-manifest
|
||||
path: plan-dist-manifest.json
|
||||
@@ -116,7 +116,7 @@ jobs:
|
||||
- name: enable windows longpaths
|
||||
run: |
|
||||
git config --global core.longpaths true
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
@@ -131,7 +131,7 @@ jobs:
|
||||
run: ${{ matrix.install_dist.run }}
|
||||
# Get the dist-manifest
|
||||
- name: Fetch local artifacts
|
||||
uses: actions/download-artifact@v7
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
pattern: artifacts-*
|
||||
path: target/distrib/
|
||||
@@ -158,7 +158,7 @@ jobs:
|
||||
|
||||
cp dist-manifest.json "$BUILD_MANIFEST_NAME"
|
||||
- name: "Upload artifacts"
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: artifacts-build-local-${{ join(matrix.targets, '_') }}
|
||||
path: |
|
||||
@@ -175,19 +175,19 @@ jobs:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
BUILD_MANIFEST_NAME: target/distrib/global-dist-manifest.json
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
- name: Install cached dist
|
||||
uses: actions/download-artifact@v7
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: cargo-dist-cache
|
||||
path: ~/.cargo/bin/
|
||||
- run: chmod +x ~/.cargo/bin/dist
|
||||
# Get all the local artifacts for the global tasks to use (for e.g. checksums)
|
||||
- name: Fetch local artifacts
|
||||
uses: actions/download-artifact@v7
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
pattern: artifacts-*
|
||||
path: target/distrib/
|
||||
@@ -205,7 +205,7 @@ jobs:
|
||||
|
||||
cp dist-manifest.json "$BUILD_MANIFEST_NAME"
|
||||
- name: "Upload artifacts"
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: artifacts-build-global
|
||||
path: |
|
||||
@@ -225,19 +225,19 @@ jobs:
|
||||
outputs:
|
||||
val: ${{ steps.host.outputs.manifest }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
- name: Install cached dist
|
||||
uses: actions/download-artifact@v7
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: cargo-dist-cache
|
||||
path: ~/.cargo/bin/
|
||||
- run: chmod +x ~/.cargo/bin/dist
|
||||
# Fetch artifacts from scratch-storage
|
||||
- name: Fetch artifacts
|
||||
uses: actions/download-artifact@v7
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
pattern: artifacts-*
|
||||
path: target/distrib/
|
||||
@@ -250,14 +250,14 @@ jobs:
|
||||
cat dist-manifest.json
|
||||
echo "manifest=$(jq -c "." dist-manifest.json)" >> "$GITHUB_OUTPUT"
|
||||
- name: "Upload dist-manifest.json"
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
# Overwrite the previous copy
|
||||
name: artifacts-dist-manifest
|
||||
path: dist-manifest.json
|
||||
# Create a GitHub Release while uploading all files to it
|
||||
- name: "Download GitHub Artifacts"
|
||||
uses: actions/download-artifact@v7
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
pattern: artifacts-*
|
||||
path: artifacts
|
||||
@@ -290,7 +290,7 @@ jobs:
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
@@ -0,0 +1,28 @@
|
||||
# spell-checker:ignore wasip
|
||||
name: WASI
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
# End the current execution if there is a new changeset in the PR.
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
|
||||
|
||||
jobs:
|
||||
test_wasi:
|
||||
name: Tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: wasm32-wasip1
|
||||
- name: check
|
||||
run: cargo check --target wasm32-wasip1
|
||||
@@ -0,0 +1,55 @@
|
||||
# See https://pre-commit.com for more information
|
||||
# See https://pre-commit.com/hooks.html for more hooks
|
||||
exclude: ^tests/fixtures/
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v6.0.0
|
||||
hooks:
|
||||
- id: check-added-large-files
|
||||
- id: check-executables-have-shebangs
|
||||
- id: check-json
|
||||
exclude: '\.vscode/(cSpell|extensions)\.json' # cSpell.json and extensions.json use comments
|
||||
- id: check-shebang-scripts-are-executable
|
||||
exclude: '.+\.rs' # would be triggered by #![some_attribute]
|
||||
- id: check-symlinks
|
||||
- id: check-toml
|
||||
- id: check-yaml
|
||||
args: [ --allow-multiple-documents ]
|
||||
- id: destroyed-symlinks
|
||||
- id: end-of-file-fixer
|
||||
- id: mixed-line-ending
|
||||
args: [ --fix=lf ]
|
||||
- id: trailing-whitespace
|
||||
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: rust-linting
|
||||
name: Rust linting
|
||||
description: Run cargo fmt on files included in the commit.
|
||||
entry: cargo +stable fmt --
|
||||
pass_filenames: true
|
||||
types: [file, rust]
|
||||
language: system
|
||||
- id: rust-clippy
|
||||
name: Rust clippy
|
||||
description: Run cargo clippy on files included in the commit.
|
||||
entry: cargo +stable clippy --workspace --all-targets --all-features -- -D warnings
|
||||
pass_filenames: false
|
||||
types: [file, rust]
|
||||
language: system
|
||||
- id: cargo-lock-check
|
||||
name: Cargo.lock sync check
|
||||
description: Ensure Cargo.lock and fuzz/Cargo.lock are up-to-date.
|
||||
entry: bash -c 'for dir in . fuzz; do ( cd "$dir" && cargo fetch --quiet ); done'
|
||||
pass_filenames: false
|
||||
files: 'Cargo\.(toml|lock)$'
|
||||
language: system
|
||||
- id: cspell
|
||||
name: Code spell checker (cspell)
|
||||
description: Run cspell to check for spelling errors (if available).
|
||||
entry: bash -c 'if command -v cspell >/dev/null 2>&1; then cspell --no-must-find-files -- "$@"; else echo "cspell not found, skipping spell check"; exit 0; fi' --
|
||||
pass_filenames: true
|
||||
language: system
|
||||
|
||||
ci:
|
||||
skip: [rust-linting, rust-clippy, cargo-lock-check, cspell]
|
||||
@@ -0,0 +1,32 @@
|
||||
# Contributing to sed
|
||||
|
||||
Hi! Welcome to uutils/sed, and thanks for wanting to contribute!
|
||||
|
||||
This project follows the shared conventions of the [uutils](https://github.com/uutils)
|
||||
organization. Before opening a pull request, please read:
|
||||
|
||||
- Our **[Review Guidelines](https://uutils.github.io/reviews/)** — what we expect
|
||||
from a pull request and how reviews are carried out.
|
||||
- Our community's [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md), if present.
|
||||
|
||||
Finally, feel free to join our [Discord](https://discord.gg/wQVJbvJ)!
|
||||
|
||||
> [!WARNING]
|
||||
> uutils is original code and cannot contain any code from GNU or other
|
||||
> strongly-licensed (GPL/LGPL) implementations. We **cannot** accept changes
|
||||
> based on the GNU source code, and you **must not link** to it either. You may
|
||||
> look at permissively-licensed implementations (MIT/BSD) and read the GNU
|
||||
> *manuals* — never the GNU *source*.
|
||||
|
||||
## In short
|
||||
|
||||
- Discuss non-trivial changes in an issue **before** writing the code.
|
||||
- Keep pull requests **small, self-contained, and descriptively titled**
|
||||
(e.g. `sed: fix ...`).
|
||||
- Make sure CI passes: tests are green, `rustfmt` is satisfied, and there are
|
||||
no `clippy` warnings.
|
||||
- Add tests for new behavior; don't let coverage regress.
|
||||
- Write small, atomic commits annotated with the component you touched.
|
||||
|
||||
See the [Review Guidelines](https://uutils.github.io/reviews/) for the full
|
||||
details.
|
||||
Generated
+489
-574
File diff suppressed because it is too large.
Load diff
+55
-35
@@ -5,8 +5,7 @@
|
||||
|
||||
[package]
|
||||
name = "sed"
|
||||
version = "0.1.1"
|
||||
authors = ["uutils developers"]
|
||||
version = "0.2.0"
|
||||
license = "MIT"
|
||||
description = "sed ~ implemented as universal (cross-platform) utils, written in Rust"
|
||||
default-run = "sed"
|
||||
@@ -29,53 +28,52 @@ sed = []
|
||||
|
||||
[workspace.dependencies]
|
||||
assert_fs = "1.1.3"
|
||||
bytesize = "2.0.0"
|
||||
chrono = { version = "0.4.37", default-features = false, features = [
|
||||
"clock",
|
||||
] }
|
||||
clap = { version = "4.4", features = ["wrap_help", "cargo"] }
|
||||
clap_complete = "4.5"
|
||||
clap_mangen = "0.2"
|
||||
divan = { package = "codspeed-divan-compat", version = "4.0.5" }
|
||||
fancy-regex = "0.17.0"
|
||||
clap_mangen = "0.3"
|
||||
divan = { package = "codspeed-divan-compat", version = "5.0.0" }
|
||||
fancy-regex = "0.18.0"
|
||||
hex = "0.4"
|
||||
libc = "0.2.153"
|
||||
memchr = "2.7.4"
|
||||
memmap2 = "0.9"
|
||||
phf = "0.13.0"
|
||||
phf_codegen = "0.13.0"
|
||||
phf = "0.14.0"
|
||||
phf_codegen = "0.14.0"
|
||||
predicates = "3.1.3"
|
||||
rand = { version = "0.9", features = ["small_rng"] }
|
||||
rand = { version = "0.10.0" }
|
||||
regex = "1.10.4"
|
||||
sha2 = "0.10"
|
||||
sysinfo = "0.38"
|
||||
sha2 = "0.11"
|
||||
tempfile = "3.10.1"
|
||||
textwrap = { version = "0.16.1", features = ["terminal_size"] }
|
||||
terminal_size = "0.4.2"
|
||||
uucore = { version = "0.5.0", features = ["libc"] }
|
||||
textwrap = { version = "0.16.1", features = ["terminal_size"] }
|
||||
uucore = { version = "0.10.0", features = ["libc"] }
|
||||
rustix = "1.1.4"
|
||||
xattr = "1.3.1"
|
||||
|
||||
|
||||
[dependencies]
|
||||
assert_fs = { workspace = true }
|
||||
clap = { workspace = true }
|
||||
clap_complete = { workspace = true }
|
||||
clap_mangen = { workspace = true }
|
||||
ctor = "0.6.0"
|
||||
fancy-regex = { workspace = true }
|
||||
memchr = { workspace = true }
|
||||
memmap2.workspace = true
|
||||
phf = { workspace = true }
|
||||
predicates = { workspace = true }
|
||||
regex = { workspace = true }
|
||||
sysinfo = { workspace = true }
|
||||
tempfile = { workspace = true }
|
||||
terminal_size = { workspace = true }
|
||||
textwrap = { workspace = true }
|
||||
uucore = { workspace = true }
|
||||
rustix = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
assert_fs = { workspace = true }
|
||||
chrono = { workspace = true }
|
||||
ctor = "1.0.0"
|
||||
divan = { workspace = true }
|
||||
hex = { workspace = true }
|
||||
libc = { workspace = true }
|
||||
@@ -85,13 +83,13 @@ regex = { workspace = true }
|
||||
sha2 = { workspace = true }
|
||||
tempfile = { workspace = true }
|
||||
uucore = { workspace = true, features = ["entries", "process", "signals", "benchmark"] }
|
||||
uutests = "0.5.0"
|
||||
uutests = "0.10.0"
|
||||
|
||||
[target.'cfg(unix)'.dev-dependencies]
|
||||
xattr = { workspace = true }
|
||||
|
||||
[target.'cfg(any(target_os = "linux", target_os = "android"))'.dev-dependencies]
|
||||
rlimit = "0.10.1"
|
||||
rlimit = "0.11.0"
|
||||
|
||||
[build-dependencies]
|
||||
phf_codegen = { workspace = true }
|
||||
@@ -107,31 +105,53 @@ path = "src/bin/sed.rs"
|
||||
name = "sed_bench"
|
||||
harness = false
|
||||
|
||||
# The default release profile. It contains all optimizations, without
|
||||
# sacrificing debug info. With this profile (like in the standard
|
||||
# release profile), the debug info and the stack traces will still be available.
|
||||
[profile.release]
|
||||
lto = true
|
||||
|
||||
# A release-like profile that is tuned to be fast, even when being fast
|
||||
# compromises on binary size. This includes aborting on panic.
|
||||
[profile.release-fast]
|
||||
inherits = "release"
|
||||
panic = "abort"
|
||||
codegen-units = 1 # should be moved to release without regression
|
||||
# should be dropped to 1 for binary size without performance drop
|
||||
codegen-units = 7
|
||||
|
||||
# A release-like profile that is as small as possible.
|
||||
[profile.release-small]
|
||||
inherits = "release-fast"
|
||||
inherits = "release"
|
||||
opt-level = "z"
|
||||
strip = true
|
||||
|
||||
[lints.clippy]
|
||||
default_trait_access = "warn"
|
||||
manual_string_new = "warn"
|
||||
all = { level = "warn", priority = -1 }
|
||||
|
||||
cargo = { level = "warn", priority = -1 }
|
||||
|
||||
pedantic = { level = "warn", priority = -1 }
|
||||
# The counts were generated with this command:
|
||||
# cargo +nightly clippy --all-targets --workspace --message-format=json --quiet \
|
||||
# | jq -r '.message.code.code | select(. != null and startswith("clippy::"))' \
|
||||
# | sort | uniq -c | sort -h -r
|
||||
missing_errors_doc = "allow" # 69
|
||||
doc_markdown = "allow" # 63
|
||||
must_use_candidate = "allow" # 56
|
||||
needless_raw_string_hashes = "allow" # 20
|
||||
needless_pass_by_value = "allow" # 15
|
||||
missing_panics_doc = "allow" # 12
|
||||
# cast_possible_truncation = "allow" # 7
|
||||
unnecessary_wraps = "allow" # 6
|
||||
match_wildcard_for_single_variants = "allow" # 4
|
||||
cast_sign_loss = "allow" # 4
|
||||
cast_possible_wrap = "allow" # 4
|
||||
uninlined_format_args = "allow" # 3
|
||||
similar_names = "allow" # 3
|
||||
used_underscore_binding = "allow" # 2
|
||||
too_many_lines = "allow" # 2
|
||||
struct_excessive_bools = "allow" # 2
|
||||
match_same_arms = "allow" # 2
|
||||
ignore_without_reason = "allow" # 2
|
||||
format_push_string = "allow" # 2
|
||||
should_panic_without_expect = "allow" # 1
|
||||
many_single_char_names = "allow" # 1
|
||||
comparison_chain = "allow"
|
||||
multiple_crate_versions = "allow"
|
||||
unnested_or_patterns = "allow"
|
||||
|
||||
restriction = { level = "allow", priority = -1 }
|
||||
cognitive_complexity = "warn"
|
||||
implicit_clone = "warn"
|
||||
range-plus-one = "warn"
|
||||
redundant-clone = "warn"
|
||||
match_bool = "warn"
|
||||
semicolon_if_nothing_returned = "warn"
|
||||
redundant_clone = "warn"
|
||||
@@ -3,7 +3,7 @@
|
||||
[](https://github.com/uutils/sed/blob/main/LICENSE)
|
||||
[](https://deps.rs/repo/github/uutils/sed)
|
||||
|
||||
[](https://codecov.io/gh/uutils/sed)
|
||||
[](https://codecov.io/gh/uutils/sed)
|
||||
|
||||
# sed
|
||||
|
||||
@@ -14,7 +14,7 @@ and other extensions.
|
||||
|
||||
## Status
|
||||
|
||||
At this state _sed_ implements all POSIX commands
|
||||
At this state _sed_ implements all [POSIX features](https://pubs.opengroup.org/onlinepubs/9799919799/)
|
||||
and can run correctly the two complex scripts of its integration tests:
|
||||
[hanoi.sed](https://github.com/uutils/sed/blob/main/tests/fixtures/sed/script/hanoi.sed) (solves the Towers of Hanoi puzzle) and
|
||||
[math.sed](https://github.com/uutils/sed/blob/main/tests/fixtures/sed/script/math.sed) (implements an arbitrary precision integer math calculator).
|
||||
@@ -26,9 +26,12 @@ Further work aims to:
|
||||
* Implement more GNU extensions,
|
||||
* Improve performance where possible.
|
||||
|
||||
## Installation
|
||||
## Installation and Use
|
||||
|
||||
Ensure you have Rust installed on your system. You can install Rust through [rustup](https://rustup.rs/).
|
||||
We provide a Linux x86_64 binary archive from the main branch at
|
||||
https://github.com/uutils/sed/releases/tag/latest-commit .
|
||||
|
||||
For other platforms, 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:
|
||||
|
||||
@@ -41,11 +44,16 @@ cargo run --release
|
||||
|
||||
The binary is named `sed` in `target/release/sed`.
|
||||
|
||||
You can also try *sed* on the web
|
||||
through the [uutils Playground](https://uutils.org//playground/)
|
||||
by clicking on the `Load sed` button.
|
||||
|
||||
## Testing
|
||||
|
||||
### GNU sed Compatibility Testing
|
||||
|
||||
Test compatibility against GNU sed using the comprehensive testsuite (47+ tests, ~10% pass rate):
|
||||
Test compatibility against GNU sed by running the upstream testsuite shell scripts
|
||||
with a lightweight gnulib test-framework shim:
|
||||
|
||||
```bash
|
||||
# Clone GNU sed testsuite (one time setup)
|
||||
@@ -54,11 +62,16 @@ git clone https://github.com/mirror/sed.git ../gnu.sed
|
||||
# Run compatibility tests
|
||||
./util/run-gnu-testsuite.sh
|
||||
|
||||
# Verbose mode shows failure details
|
||||
./util/run-gnu-testsuite.sh -v
|
||||
|
||||
# Generate JSON results for CI
|
||||
./util/run-gnu-testsuite.sh --json-output results.json
|
||||
```
|
||||
|
||||
The testsuite extracts test cases from the GNU sed repository and tests them against expected outputs.
|
||||
The harness executes each `.sh` test from the GNU sed testsuite directly, injecting
|
||||
our Rust sed binary via `PATH` and providing shim implementations of the gnulib test
|
||||
framework functions (`compare_`, `returns_`, `skip_`, etc.).
|
||||
|
||||
### Unit Tests
|
||||
|
||||
@@ -72,21 +85,25 @@ cargo test
|
||||
* Spaces can precede a regular expression modifier.
|
||||
* `I` can be used in as a synonym for the `i` (case insensitive) substitution
|
||||
flag.
|
||||
* `M` and `m` substitution flags allow multi-line matching.
|
||||
* In addition to `\n`, other escape sequences (octal, hex, C) are supported
|
||||
in the strings of the `y` command.
|
||||
Under POSIX these yield undefined behavior.
|
||||
* The `a`, `c`, and `i` commands do not require an initial backslash,
|
||||
allow text to appear on the same line, and support escape sequences
|
||||
in the specified text.
|
||||
* The `a`, `i`, `=`, `l`, `q` and `r` commands support address range as an extension to POSIX.
|
||||
* The substitution command replacement group `\0` is a synonym for &.
|
||||
* A `Q` command (optionally followed by an exit code) quits immediately.
|
||||
* The `q` command can be optionally followed by an exit code.
|
||||
* The `l` command can be optionally followed by the output width.
|
||||
* The `--follow-symlinks` flag for in-place editing.
|
||||
* The `--follow-symlinks` option for in-place editing.
|
||||
* The `--sandbox` option that limits potentially destructive commands.
|
||||
* Address 0 can be used to specify an address range that is already
|
||||
active on line 1 and can finish with the specified regular expression.
|
||||
* Address steps can be specified in the form of start~step and start,~step
|
||||
ranges.
|
||||
* Address 0 can be used in the `r` command to prepend a file.
|
||||
|
||||
### Supported BSD and GNU extensions
|
||||
* The second address in a range can be specified as a relative address with +N.
|
||||
@@ -99,12 +116,19 @@ cargo test
|
||||
sequences.
|
||||
|
||||
### Incompatibilities
|
||||
* The input is assumed to be valid UTF-8 (this includes 7-bit ASCII).
|
||||
If the input is in another code page, consider converting it through UTF-8
|
||||
in order to avoid errors on invalid UTF-8 sequences and for the correct
|
||||
handling of regular expressions.
|
||||
This _sed_ program can also handle arbitrary byte sequences if no part of the
|
||||
input is treated as string.
|
||||
* Similarly to GNU _sed_, input is processed as raw bytes or as valid UTF-8
|
||||
(this includes 7-bit ASCII) based on the locale as specified by the
|
||||
`LC_ALL`, `LC_CTYPE`, and `LANG` environment variables,
|
||||
with the default being byte processing.
|
||||
However, in contrast with GNU _sed_, other locales (e.g. ISO-8859-1)
|
||||
are not supported. If the input is in another code page or encoding
|
||||
and requires locale-specific processing (e.g. ignore/map case,
|
||||
character classes), consider converting it through UTF-8 to ensure
|
||||
the correct handling of locale-specific regular expressions.
|
||||
This _sed_ program can also handle arbitrary byte sequences
|
||||
if no part of the input requires treating it as a Rust String.
|
||||
* Back-references aren't supported when input is processed as bytes
|
||||
(`LC_ALL=C`).
|
||||
* The command will report an error and fail if duplicate labels are found
|
||||
in the script.
|
||||
This matches the BSD behavior. The GNU version accepts duplicate labels.
|
||||
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
We provide security updates only for the latest released version of `uutils/sed`.
|
||||
Older versions may not receive patches.
|
||||
If you are using a version packaged by your Linux distribution, please check with your distribution maintainers for their update policy.
|
||||
|
||||
---
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
**Do not open public GitHub issues for security vulnerabilities.**
|
||||
This prevents accidental disclosure before a fix is available.
|
||||
|
||||
Instead, please use the following method:
|
||||
|
||||
- **Email:** [sylvestre@debian.org](mailto:Sylvestre@debian.org)
|
||||
- **Encryption (optional):** You may encrypt your report using our PGP key:
|
||||
Fingerprint: B60D B599 4D39 BEC4 D1A9 5CCF 7E65 28DA 752F 1BE1
|
||||
---
|
||||
|
||||
### What to Include in Your Report
|
||||
|
||||
To help us investigate and resolve the issue quickly, please include as much detail as possible:
|
||||
|
||||
- **Type of issue:** e.g. privilege escalation, information disclosure.
|
||||
- **Location in the source:** file path, commit hash, branch, or tag.
|
||||
- **Steps to reproduce:** exact commands, test cases, or scripts.
|
||||
- **Special configuration:** any flags, environment variables, or system setup required.
|
||||
- **Affected systems:** OS/distribution and version(s) where the issue occurs.
|
||||
- **Impact:** your assessment of the potential severity (DoS, RCE, data leak, etc.).
|
||||
|
||||
---
|
||||
|
||||
## Disclosure Policy
|
||||
|
||||
We follow a **Coordinated Vulnerability Disclosure (CVD)** process:
|
||||
|
||||
1. We will acknowledge receipt of your report within **10 days**.
|
||||
2. We will investigate, reproduce, and assess the issue.
|
||||
3. We will provide a timeline for developing and releasing a fix.
|
||||
4. Once a fix is available, we will publish a GitHub Security Advisory.
|
||||
5. You will be credited in the advisory unless you request anonymity.
|
||||
Generated
+476
-583
File diff suppressed because it is too large.
Load diff
+4
-4
@@ -12,10 +12,10 @@ console = "0.16.0"
|
||||
libfuzzer-sys = "0.4.7"
|
||||
libc = "0.2.153"
|
||||
tempfile = "3.15.0"
|
||||
rand = { version = "0.9.0", features = ["small_rng"] }
|
||||
similar = "2.5.0"
|
||||
uucore = { version = "0.5.0", features = ["libc"] }
|
||||
uufuzz = "0.5.0"
|
||||
rand = { version = "0.10.0" }
|
||||
similar = "3.0.0"
|
||||
uucore = { version = "0.10.0", features = ["libc"] }
|
||||
uufuzz = "0.10.0"
|
||||
|
||||
sed = { path = ".." }
|
||||
|
||||
|
||||
+164
-78
@@ -52,6 +52,8 @@ pub struct ProcessingContext {
|
||||
pub last_file: bool,
|
||||
/// Stop processing further input.
|
||||
pub stop_processing: bool,
|
||||
/// Whether sed operates on bytes or UTF-8 characters
|
||||
pub character_mode: CharacterMode,
|
||||
/// Previously compiled RE, saved for reuse when specifying an empty RE
|
||||
pub saved_regex: Option<Regex>,
|
||||
/// Modification of input processing action
|
||||
@@ -59,7 +61,7 @@ pub struct ProcessingContext {
|
||||
// command.
|
||||
pub input_action: Option<InputAction>,
|
||||
/// Hold space
|
||||
pub hold: StringSpace,
|
||||
pub hold: ByteSpace,
|
||||
/// Nesting of { } at compile time
|
||||
pub parsed_block_nesting: usize,
|
||||
/// Command associated with each label
|
||||
@@ -75,14 +77,22 @@ pub struct ProcessingContext {
|
||||
#[derive(Clone, Debug)]
|
||||
/// Elements that shall be appended at the end of each command processing cycle
|
||||
pub enum AppendElement {
|
||||
Text(Rc<str>), // The specified text string
|
||||
Path(PathBuf), // The contents of the specified file path
|
||||
Text(Rc<[u8]>), // The specified text bytes
|
||||
Path(PathBuf), // The contents of the specified file path
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
||||
/// Whether sed operates on bytes or UTF-8 characters
|
||||
pub enum CharacterMode {
|
||||
Byte, // Interpret data as arbitrary bytes (C/POSIX locale).
|
||||
#[default]
|
||||
Utf8, // Interpret data as UTF-8 characters (UTF-8 locale).
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
/// A space mirroring IOChunk, but only with a String
|
||||
pub struct StringSpace {
|
||||
pub content: String, // Line content without newline
|
||||
/// A space mirroring IOChunk without mmap-backed storage.
|
||||
pub struct ByteSpace {
|
||||
pub content: Vec<u8>, // Line content without newline
|
||||
pub has_newline: bool, // True if \n-terminated
|
||||
}
|
||||
|
||||
@@ -100,9 +110,19 @@ pub enum Address {
|
||||
#[derive(Debug)]
|
||||
/// A single part of an RE replacement
|
||||
pub enum ReplacementPart {
|
||||
Literal(String), // Normal text
|
||||
WholeMatch, // &
|
||||
Group(u32), // \1 to \9
|
||||
Literal(Vec<u8>), // Normal text
|
||||
WholeMatch, // &
|
||||
Group(u32), // \1 to \9
|
||||
}
|
||||
|
||||
// The maximum value allowed in regex quantifier
|
||||
pub const RE_DUP_MAX: usize = 32767;
|
||||
|
||||
/// Regex modes (BRE or ERE)
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum RegexMode {
|
||||
Basic,
|
||||
Extended,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -142,8 +162,8 @@ impl ReplacementTemplate {
|
||||
/// let result = regex.replace_all(input, |caps: &Captures| {
|
||||
/// template.apply_captures(&command, caps) });
|
||||
/// Returns an error if a backreference in the template was not matched by the RE.
|
||||
pub fn apply_captures(&self, command: &Command, caps: &Captures) -> UResult<String> {
|
||||
let mut result = String::new();
|
||||
pub fn apply_captures(&self, command: &Command, caps: &Captures) -> UResult<Vec<u8>> {
|
||||
let mut result = Vec::new();
|
||||
|
||||
// Invalid group numbers may end here through (unkown at compile time)
|
||||
// reused REs.
|
||||
@@ -159,15 +179,17 @@ impl ReplacementTemplate {
|
||||
|
||||
for part in &self.parts {
|
||||
match part {
|
||||
ReplacementPart::Literal(s) => result.push_str(s),
|
||||
ReplacementPart::Literal(s) => result.extend_from_slice(s),
|
||||
|
||||
ReplacementPart::WholeMatch => {
|
||||
result.push_str(caps.get(0)?.map(|m| m.as_str()).unwrap_or(""));
|
||||
result
|
||||
.extend_from_slice(caps.get(0)?.map(|m| m.as_bytes()).unwrap_or_default());
|
||||
}
|
||||
|
||||
ReplacementPart::Group(n) => {
|
||||
let i: usize = (*n).try_into().unwrap();
|
||||
result.push_str(caps.get(i)?.map(|m| m.as_str()).unwrap_or(""));
|
||||
result
|
||||
.extend_from_slice(caps.get(i)?.map(|m| m.as_bytes()).unwrap_or_default());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -176,14 +198,14 @@ impl ReplacementTemplate {
|
||||
}
|
||||
|
||||
/// Apply the template to the given RE single match.
|
||||
pub fn apply_match(&self, m: &Match) -> String {
|
||||
let mut result = String::new();
|
||||
pub fn apply_match(&self, m: &Match) -> Vec<u8> {
|
||||
let mut result = Vec::new();
|
||||
|
||||
for part in &self.parts {
|
||||
match part {
|
||||
ReplacementPart::Literal(s) => result.push_str(s),
|
||||
ReplacementPart::Literal(s) => result.extend_from_slice(s),
|
||||
|
||||
ReplacementPart::WholeMatch => result.push_str(m.as_str()),
|
||||
ReplacementPart::WholeMatch => result.extend_from_slice(m.as_bytes()),
|
||||
|
||||
ReplacementPart::Group(_) => {
|
||||
panic!("unexpected Regex group replacement")
|
||||
@@ -201,10 +223,20 @@ pub struct Substitution {
|
||||
pub replacement: ReplacementTemplate, // Specified broken-down replacement
|
||||
pub occurrence: usize, // Which occurrence to substitute
|
||||
pub print_flag: bool, // True if 'p' flag
|
||||
pub p_before_e: bool, // True if 'p' appears before 'e'
|
||||
pub ignore_case: bool, // True if 'I' flag
|
||||
pub execute: bool, // True if 'e' flag (GNU extension)
|
||||
pub multiline: bool, // True if 'm' or 'M' flag (GNU extension)
|
||||
pub write_file: Option<Rc<RefCell<NamedWriter>>>, // Writer to file if 'w' flag is used
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
/// Result of parsing a transliteration string in byte or character mode.
|
||||
pub enum ParsedTransliteration {
|
||||
Bytes(Vec<u8>),
|
||||
Text(String),
|
||||
}
|
||||
|
||||
/// The block of the first and most common Unicode characters:
|
||||
/// ASCII, Latin Extended, Greek, Curillic, Coptic, Arabic, etc.
|
||||
/// It comprises all UCS-2 characters. We use a fast lookup array for these.
|
||||
@@ -213,20 +245,28 @@ const COMMON_UNICODE: usize = 2048;
|
||||
#[derive(Debug)]
|
||||
/// Transliteration command (y)
|
||||
pub struct Transliteration {
|
||||
fast: [char; COMMON_UNICODE],
|
||||
slow: HashMap<char, char>,
|
||||
byte_fast: [u8; 256],
|
||||
unicode_fast: [char; COMMON_UNICODE],
|
||||
unicode_slow: HashMap<char, char>,
|
||||
pub(crate) is_byte_identity: bool,
|
||||
}
|
||||
|
||||
impl Default for Transliteration {
|
||||
/// Create a new Transliteration with identity mapping for the fast-path.
|
||||
fn default() -> Self {
|
||||
let mut fast = ['\0'; COMMON_UNICODE];
|
||||
for (i, slot) in fast.iter_mut().enumerate() {
|
||||
*slot = char::from_u32(i as u32).unwrap_or('\0');
|
||||
let mut fast = [0u8; 256];
|
||||
for (slot, value) in fast.iter_mut().zip(0..=u8::MAX) {
|
||||
*slot = value;
|
||||
}
|
||||
let mut unicode_fast = ['\0'; COMMON_UNICODE];
|
||||
for (slot, cp) in unicode_fast.iter_mut().zip(0u32..) {
|
||||
*slot = char::from_u32(cp).unwrap_or('\0');
|
||||
}
|
||||
Self {
|
||||
fast,
|
||||
slow: HashMap::new(),
|
||||
byte_fast: fast,
|
||||
unicode_fast,
|
||||
unicode_slow: HashMap::new(),
|
||||
is_byte_identity: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -241,25 +281,44 @@ impl Transliteration {
|
||||
result
|
||||
}
|
||||
|
||||
/// Create through byte mappings from `source` to `target`.
|
||||
pub fn from_bytes(source: &[u8], target: &[u8]) -> Self {
|
||||
let mut result = Self::default();
|
||||
for (&from, &to) in source.iter().zip(target) {
|
||||
result.byte_fast[from as usize] = to;
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/// Set a transliteration mapping from one character to another.
|
||||
fn insert(&mut self, from: char, to: char) {
|
||||
let cp = from as usize;
|
||||
if cp < COMMON_UNICODE {
|
||||
self.fast[cp] = to;
|
||||
self.unicode_fast[cp] = to;
|
||||
} else {
|
||||
self.slow.insert(from, to);
|
||||
self.unicode_slow.insert(from, to);
|
||||
}
|
||||
if from.is_ascii() && to.is_ascii() {
|
||||
self.byte_fast[from as usize] = to as u8;
|
||||
} else {
|
||||
self.is_byte_identity = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Look up a character transliteration.
|
||||
pub fn lookup(&self, ch: char) -> char {
|
||||
pub fn lookup_char(&self, ch: char) -> char {
|
||||
let cp = ch as usize;
|
||||
if cp < COMMON_UNICODE {
|
||||
self.fast[cp]
|
||||
self.unicode_fast[cp]
|
||||
} else {
|
||||
self.slow.get(&ch).copied().unwrap_or(ch)
|
||||
self.unicode_slow.get(&ch).copied().unwrap_or(ch)
|
||||
}
|
||||
}
|
||||
|
||||
/// Fast byte lookup for pure ASCII transliterations.
|
||||
pub fn lookup_byte(&self, byte: u8) -> u8 {
|
||||
self.byte_fast[byte as usize]
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -302,16 +361,16 @@ impl Command {
|
||||
|
||||
#[derive(Debug)]
|
||||
/// Command-specific data
|
||||
/// After parsing, t, b Label elements are converted into BranchTarget ones.
|
||||
/// After parsing, t, T, b Label elements are converted into BranchTarget ones.
|
||||
pub enum CommandData {
|
||||
None,
|
||||
BranchTarget(Option<Rc<RefCell<Command>>>), // Commands for 'b', 't', '{'
|
||||
Label(Option<String>), // Label name for 'b', 't', ':'
|
||||
BranchTarget(Option<Rc<RefCell<Command>>>), // Commands for 'b', 't', 'T', '{'
|
||||
Label(Option<String>), // Label name for 'b', 't', 'T', ':'
|
||||
Path(PathBuf), // File path for 'r'
|
||||
NamedWriter(Rc<RefCell<NamedWriter>>), // File output for 'w'
|
||||
Number(usize), // Number for 'l', 'q', 'Q' (GNU)
|
||||
Substitution(Box<Substitution>), // Substitute command 's'
|
||||
Text(Rc<str>), // Text for 'a', 'c', 'i'
|
||||
Text(Rc<[u8]>), // Text for 'a', 'c', 'i'
|
||||
Transliteration(Box<Transliteration>), // Transliteration command 'y'
|
||||
}
|
||||
|
||||
@@ -328,7 +387,7 @@ pub struct InputAction {
|
||||
/// Next command to execute (rather than commands from start)
|
||||
pub next_command: Option<Rc<RefCell<Command>>>,
|
||||
/// Data to prepend to the read contents
|
||||
pub prepend: String,
|
||||
pub prepend: Vec<u8>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -338,7 +397,7 @@ mod tests {
|
||||
|
||||
// Return the captures for the RE applied to the specified string
|
||||
fn caps_for<'a>(re: &str, chunk: &'a mut IOChunk) -> Captures<'a> {
|
||||
Regex::new(re)
|
||||
Regex::new(re, CharacterMode::Utf8)
|
||||
.unwrap()
|
||||
.captures(chunk)
|
||||
.unwrap()
|
||||
@@ -354,26 +413,26 @@ mod tests {
|
||||
let cmd = Command::default();
|
||||
|
||||
let result = template.apply_captures(&cmd, &caps).unwrap();
|
||||
assert_eq!(result, "");
|
||||
assert_eq!(result, b"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
// s/abc/hello/
|
||||
fn test_literal_only() {
|
||||
let template = ReplacementTemplate::new(vec![ReplacementPart::Literal("hello".into())]);
|
||||
let template = ReplacementTemplate::new(vec![ReplacementPart::Literal(b"hello".to_vec())]);
|
||||
let input = &mut IOChunk::new_from_str("abc");
|
||||
let caps = caps_for("abc", input);
|
||||
let cmd = Command::default();
|
||||
|
||||
let result = template.apply_captures(&cmd, &caps).unwrap();
|
||||
assert_eq!(result, "hello");
|
||||
assert_eq!(result, b"hello");
|
||||
}
|
||||
|
||||
#[test]
|
||||
// s/foo\d+/got: &/
|
||||
fn test_whole_match() {
|
||||
let template = ReplacementTemplate::new(vec![
|
||||
ReplacementPart::Literal("got: ".into()),
|
||||
ReplacementPart::Literal(b"got: ".to_vec()),
|
||||
ReplacementPart::WholeMatch,
|
||||
]);
|
||||
let input = &mut IOChunk::new_from_str("foo42");
|
||||
@@ -381,14 +440,26 @@ mod tests {
|
||||
let cmd = Command::default();
|
||||
|
||||
let result = template.apply_captures(&cmd, &caps).unwrap();
|
||||
assert_eq!(result, "got: foo42");
|
||||
assert_eq!(result, b"got: foo42");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_apply_match_uses_matched_bytes() {
|
||||
let template = ReplacementTemplate::new(vec![
|
||||
ReplacementPart::Literal(b"<".to_vec()),
|
||||
ReplacementPart::WholeMatch,
|
||||
ReplacementPart::Literal(b">".to_vec()),
|
||||
]);
|
||||
let m = Match::from_bytes(1, 3, b"\xE9x");
|
||||
|
||||
assert_eq!(template.apply_match(&m), b"<\xE9x>");
|
||||
}
|
||||
|
||||
#[test]
|
||||
// s/foo(\d+)/number: \1/
|
||||
fn test_backreference() {
|
||||
let template = ReplacementTemplate::new(vec![
|
||||
ReplacementPart::Literal("number: ".into()),
|
||||
ReplacementPart::Literal(b"number: ".to_vec()),
|
||||
ReplacementPart::Group(1),
|
||||
]);
|
||||
let input = &mut IOChunk::new_from_str("foo42");
|
||||
@@ -396,16 +467,16 @@ mod tests {
|
||||
let cmd = Command::default();
|
||||
|
||||
let result = template.apply_captures(&cmd, &caps).unwrap();
|
||||
assert_eq!(result, "number: 42");
|
||||
assert_eq!(result, b"number: 42");
|
||||
}
|
||||
|
||||
#[test]
|
||||
// s/(\w+):(\d+)/key: \1, value: \2/
|
||||
fn test_multiple_parts() {
|
||||
let template = ReplacementTemplate::new(vec![
|
||||
ReplacementPart::Literal("key: ".into()),
|
||||
ReplacementPart::Literal(b"key: ".to_vec()),
|
||||
ReplacementPart::Group(1),
|
||||
ReplacementPart::Literal(", value: ".into()),
|
||||
ReplacementPart::Literal(b", value: ".to_vec()),
|
||||
ReplacementPart::Group(2),
|
||||
]);
|
||||
let input = &mut IOChunk::new_from_str("x:123");
|
||||
@@ -413,16 +484,16 @@ mod tests {
|
||||
let cmd = Command::default();
|
||||
|
||||
let result = template.apply_captures(&cmd, &caps).unwrap();
|
||||
assert_eq!(result, "key: x, value: 123");
|
||||
assert_eq!(result, b"key: x, value: 123");
|
||||
}
|
||||
|
||||
#[test]
|
||||
// s/(\w+):(\d+)/key: \1, value: \3/
|
||||
fn test_invalid_group() {
|
||||
let template = ReplacementTemplate::new(vec![
|
||||
ReplacementPart::Literal("key: ".into()),
|
||||
ReplacementPart::Literal(b"key: ".to_vec()),
|
||||
ReplacementPart::Group(1),
|
||||
ReplacementPart::Literal(", value: ".into()),
|
||||
ReplacementPart::Literal(b", value: ".to_vec()),
|
||||
ReplacementPart::Group(3),
|
||||
]);
|
||||
let input = &mut IOChunk::new_from_str("x:123");
|
||||
@@ -440,11 +511,11 @@ mod tests {
|
||||
#[test]
|
||||
fn test_max_group_number_with_groups() {
|
||||
let template = ReplacementTemplate::new(vec![
|
||||
ReplacementPart::Literal("a".into()),
|
||||
ReplacementPart::Literal(b"a".to_vec()),
|
||||
ReplacementPart::Group(2),
|
||||
ReplacementPart::WholeMatch,
|
||||
ReplacementPart::Group(5),
|
||||
ReplacementPart::Literal("z".into()),
|
||||
ReplacementPart::Literal(b"z".to_vec()),
|
||||
]);
|
||||
assert_eq!(template.max_group_number, 5);
|
||||
}
|
||||
@@ -452,9 +523,9 @@ mod tests {
|
||||
#[test]
|
||||
fn test_max_group_number_without_groups() {
|
||||
let template = ReplacementTemplate::new(vec![
|
||||
ReplacementPart::Literal("no".into()),
|
||||
ReplacementPart::Literal(b"no".to_vec()),
|
||||
ReplacementPart::WholeMatch,
|
||||
ReplacementPart::Literal("groups".into()),
|
||||
ReplacementPart::Literal(b"groups".to_vec()),
|
||||
]);
|
||||
assert_eq!(template.max_group_number, 0);
|
||||
}
|
||||
@@ -464,16 +535,31 @@ mod tests {
|
||||
#[test]
|
||||
fn test_identity_lookup_fast_path() {
|
||||
let t = Transliteration::default();
|
||||
assert_eq!(t.lookup('A'), 'A');
|
||||
assert_eq!(t.lookup('z'), 'z');
|
||||
assert_eq!(t.lookup('\u{07FF}'), '\u{07FF}'); // highest 2-byte UTF-8 char
|
||||
assert_eq!(t.lookup_char('A'), 'A');
|
||||
assert_eq!(t.lookup_char('z'), 'z');
|
||||
assert_eq!(t.lookup_char('\u{07FF}'), '\u{07FF}'); // highest 2-byte UTF-8 char
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_identity_lookup_slow_path() {
|
||||
let t = Transliteration::default();
|
||||
assert_eq!(t.lookup('\u{0800}'), '\u{0800}'); // just outside fast path
|
||||
assert_eq!(t.lookup('\u{1F600}'), '\u{1F600}'); // 😀
|
||||
assert_eq!(t.lookup_char('\u{0800}'), '\u{0800}'); // just outside fast path
|
||||
assert_eq!(t.lookup_char('\u{1F600}'), '\u{1F600}'); // 😀
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_bytes_and_lookup_byte() {
|
||||
let t = Transliteration::from_bytes(b"a\xE9", b"Z!");
|
||||
assert_eq!(t.lookup_byte(b'a'), b'Z');
|
||||
assert_eq!(t.lookup_byte(0xE9), b'!');
|
||||
assert_eq!(t.lookup_byte(b'b'), b'b');
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_byte_identity_tracks_non_ascii_character_mappings() {
|
||||
assert!(Transliteration::from_strings("ab", "xy").is_byte_identity);
|
||||
assert!(!Transliteration::from_strings("aé", "xy").is_byte_identity);
|
||||
assert!(!Transliteration::from_strings("ab", "xé").is_byte_identity);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -481,38 +567,38 @@ mod tests {
|
||||
let mut t = Transliteration::default();
|
||||
t.insert('a', 'α');
|
||||
t.insert('b', 'β');
|
||||
assert_eq!(t.lookup('a'), 'α');
|
||||
assert_eq!(t.lookup('b'), 'β');
|
||||
assert_eq!(t.lookup('c'), 'c'); // unchanged
|
||||
assert_eq!(t.lookup_char('a'), 'α');
|
||||
assert_eq!(t.lookup_char('b'), 'β');
|
||||
assert_eq!(t.lookup_char('c'), 'c'); // unchanged
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_insert_and_lookup_slow_path() {
|
||||
let mut t = Transliteration::default();
|
||||
t.insert('🦀', 'c'); // U+1F980 Crab emoji -> 'c'
|
||||
assert_eq!(t.lookup('🦀'), 'c');
|
||||
assert_eq!(t.lookup('🦁'), '🦁'); // unchanged
|
||||
assert_eq!(t.lookup_char('🦀'), 'c');
|
||||
assert_eq!(t.lookup_char('🦁'), '🦁'); // unchanged
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_overwrite_mapping() {
|
||||
let mut t = Transliteration::default();
|
||||
t.insert('x', '1');
|
||||
assert_eq!(t.lookup('x'), '1');
|
||||
assert_eq!(t.lookup_char('x'), '1');
|
||||
t.insert('x', '2');
|
||||
assert_eq!(t.lookup('x'), '2');
|
||||
assert_eq!(t.lookup_char('x'), '2');
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_all_fast_path_mapped_to_space() {
|
||||
let mut t = Transliteration::default();
|
||||
for cp in 0..COMMON_UNICODE {
|
||||
if let Some(ch) = char::from_u32(cp as u32) {
|
||||
if let Some(ch) = u32::try_from(cp).ok().and_then(char::from_u32) {
|
||||
t.insert(ch, ' ');
|
||||
}
|
||||
}
|
||||
assert_eq!(t.lookup('A'), ' ');
|
||||
assert_eq!(t.lookup('\u{07FF}'), ' ');
|
||||
assert_eq!(t.lookup_char('A'), ' ');
|
||||
assert_eq!(t.lookup_char('\u{07FF}'), ' ');
|
||||
}
|
||||
|
||||
// from_strings
|
||||
@@ -520,11 +606,11 @@ mod tests {
|
||||
fn test_basic_transliteration() {
|
||||
let t = Transliteration::from_strings("abcδ", "1234");
|
||||
|
||||
assert_eq!(t.lookup('a'), '1');
|
||||
assert_eq!(t.lookup('b'), '2');
|
||||
assert_eq!(t.lookup('c'), '3');
|
||||
assert_eq!(t.lookup('δ'), '4');
|
||||
assert_eq!(t.lookup('e'), 'e'); // not mapped, fallback
|
||||
assert_eq!(t.lookup_char('a'), '1');
|
||||
assert_eq!(t.lookup_char('b'), '2');
|
||||
assert_eq!(t.lookup_char('c'), '3');
|
||||
assert_eq!(t.lookup_char('δ'), '4');
|
||||
assert_eq!(t.lookup_char('e'), 'e'); // not mapped, fallback
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -533,16 +619,16 @@ mod tests {
|
||||
let target = "e文c";
|
||||
let t = Transliteration::from_strings(source, target);
|
||||
|
||||
assert_eq!(t.lookup('é'), 'e');
|
||||
assert_eq!(t.lookup('漢'), '文');
|
||||
assert_eq!(t.lookup('🦀'), 'c');
|
||||
assert_eq!(t.lookup('x'), 'x'); // fast fallback
|
||||
assert_eq!(t.lookup('文'), '文'); // slow fallback
|
||||
assert_eq!(t.lookup_char('é'), 'e');
|
||||
assert_eq!(t.lookup_char('漢'), '文');
|
||||
assert_eq!(t.lookup_char('🦀'), 'c');
|
||||
assert_eq!(t.lookup_char('x'), 'x'); // fast fallback
|
||||
assert_eq!(t.lookup_char('文'), '文'); // slow fallback
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_overwrite_fast_path() {
|
||||
let t = Transliteration::from_strings("aa", "12");
|
||||
assert_eq!(t.lookup('a'), '2'); // last mapping wins
|
||||
assert_eq!(t.lookup_char('a'), '2'); // last mapping wins
|
||||
}
|
||||
}
|
||||
+934
-222
File diff suppressed because it is too large.
Load diff
Loaded 20 of 45 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user