mirror of
https://github.com/uutils/sed.git
synced 2026-06-10 16:14:15 -07:00
Compare commits
157
Commits
latest-commit
...
main
@@ -18,7 +18,7 @@ jobs:
|
|||||||
github.event.workflow_run.event == 'pull_request'
|
github.event.workflow_run.event == 'pull_request'
|
||||||
steps:
|
steps:
|
||||||
- name: 'Download artifact'
|
- name: 'Download artifact'
|
||||||
uses: actions/github-script@v8
|
uses: actions/github-script@v9
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
// List all artifacts from GnuTests
|
// List all artifacts from GnuTests
|
||||||
@@ -49,7 +49,7 @@ jobs:
|
|||||||
- run: unzip comment.zip || echo "Failed to unzip comment artifact"
|
- run: unzip comment.zip || echo "Failed to unzip comment artifact"
|
||||||
|
|
||||||
- name: 'Comment on PR'
|
- name: 'Comment on PR'
|
||||||
uses: actions/github-script@v8
|
uses: actions/github-script@v9
|
||||||
with:
|
with:
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
script: |
|
script: |
|
||||||
|
|||||||
@@ -56,16 +56,18 @@ jobs:
|
|||||||
## Build Rust sed binary
|
## Build Rust sed binary
|
||||||
cd 'sed'
|
cd 'sed'
|
||||||
cargo build --config=profile.release.strip=true --profile=release #-fast
|
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
|
- 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'
|
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||||
with:
|
with:
|
||||||
tag_name: latest-commit
|
tag_name: latest-commit
|
||||||
|
body: |
|
||||||
|
commit: ${{ github.sha }}
|
||||||
draft: false
|
draft: false
|
||||||
prerelease: true
|
prerelease: true
|
||||||
files: |
|
files: |
|
||||||
sed-x86_64-unknown-linux-gnu.zst
|
sed-x86_64-unknown-linux-gnu.tar.zst
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
@@ -75,8 +77,8 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
## Run GNU sed testsuite using our script
|
## Run GNU sed testsuite using our script
|
||||||
cd 'sed'
|
cd 'sed'
|
||||||
# Set GNU testsuite directory
|
# Set GNU sed source directory (testsuite is at gnu.sed/testsuite/)
|
||||||
export GNU_TESTSUITE_DIR="../gnu.sed/testsuite"
|
export GNU_SED_DIR="../gnu.sed"
|
||||||
# Run tests with JSON output
|
# Run tests with JSON output
|
||||||
./util/run-gnu-testsuite.sh --json-output "${{ env.TEST_FULL_SUMMARY_FILE }}" || true
|
./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"
|
find . -name "*json*" -type f || echo "No JSON files found"
|
||||||
|
|
||||||
- name: Upload full json results
|
- name: Upload full json results
|
||||||
uses: actions/upload-artifact@v6
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: sed-gnu-full-result
|
name: sed-gnu-full-result
|
||||||
path: sed/${{ env.TEST_FULL_SUMMARY_FILE }}
|
path: sed/${{ env.TEST_FULL_SUMMARY_FILE }}
|
||||||
@@ -99,7 +101,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Upload test logs
|
- name: Upload test logs
|
||||||
if: always()
|
if: always()
|
||||||
uses: actions/upload-artifact@v6
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: test-logs
|
name: test-logs
|
||||||
path: |
|
path: |
|
||||||
@@ -132,7 +134,7 @@ jobs:
|
|||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Retrieve reference artifacts
|
- name: Retrieve reference artifacts
|
||||||
uses: dawidd6/action-download-artifact@v12
|
uses: dawidd6/action-download-artifact@v21
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
with:
|
with:
|
||||||
workflow: GnuTests.yml
|
workflow: GnuTests.yml
|
||||||
@@ -142,7 +144,7 @@ jobs:
|
|||||||
if_no_artifact_found: warn
|
if_no_artifact_found: warn
|
||||||
|
|
||||||
- name: Download full json results
|
- name: Download full json results
|
||||||
uses: actions/download-artifact@v7
|
uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: sed-gnu-full-result
|
name: sed-gnu-full-result
|
||||||
path: results
|
path: results
|
||||||
@@ -203,13 +205,13 @@ jobs:
|
|||||||
outputs HASH TOTAL PASS FAIL SKIP
|
outputs HASH TOTAL PASS FAIL SKIP
|
||||||
|
|
||||||
- name: Upload SHA1/ID of 'test-summary'
|
- name: Upload SHA1/ID of 'test-summary'
|
||||||
uses: actions/upload-artifact@v6
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: "${{ steps.summary.outputs.HASH }}"
|
name: "${{ steps.summary.outputs.HASH }}"
|
||||||
path: "${{ steps.vars.outputs.TEST_SUMMARY_FILE }}"
|
path: "${{ steps.vars.outputs.TEST_SUMMARY_FILE }}"
|
||||||
|
|
||||||
- name: Upload test results summary
|
- name: Upload test results summary
|
||||||
uses: actions/upload-artifact@v6
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: test-summary
|
name: test-summary
|
||||||
path: "${{ steps.vars.outputs.TEST_SUMMARY_FILE }}"
|
path: "${{ steps.vars.outputs.TEST_SUMMARY_FILE }}"
|
||||||
@@ -262,7 +264,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Upload comparison log (for GnuComment workflow)
|
- name: Upload comparison log (for GnuComment workflow)
|
||||||
if: success() || failure()
|
if: success() || failure()
|
||||||
uses: actions/upload-artifact@v6
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: comment
|
name: comment
|
||||||
path: reference/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@v6
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
- uses: rustsec/audit-check@v2
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
@@ -29,21 +29,16 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Install system dependencies
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
sudo apt-get -y update
|
|
||||||
|
|
||||||
- uses: dtolnay/rust-toolchain@stable
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
|
|
||||||
- uses: Swatinem/rust-cache@v2
|
- uses: Swatinem/rust-cache@v2
|
||||||
|
|
||||||
- name: Run sccache-cache
|
- name: Run sccache-cache
|
||||||
uses: mozilla-actions/sccache-action@v0.0.9
|
uses: mozilla-actions/sccache-action@v0.0.10
|
||||||
|
|
||||||
- name: Install cargo-codspeed
|
- name: Install tools
|
||||||
shell: bash
|
uses: taiki-e/install-action@v2
|
||||||
run: cargo install cargo-codspeed --locked
|
with:
|
||||||
|
tool: cargo-codspeed
|
||||||
|
|
||||||
- name: Build benchmarks for ${{ matrix.benchmark-target.package }}
|
- name: Build benchmarks for ${{ matrix.benchmark-target.package }}
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|||||||
@@ -17,6 +17,27 @@ jobs:
|
|||||||
- uses: dtolnay/rust-toolchain@stable
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
- run: cargo check
|
- 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@v6
|
||||||
|
- 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:
|
test:
|
||||||
name: cargo test
|
name: cargo test
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
@@ -109,7 +130,7 @@ jobs:
|
|||||||
ls -al
|
ls -al
|
||||||
echo "report=${COVERAGE_REPORT_FILE}" >> $GITHUB_OUTPUT
|
echo "report=${COVERAGE_REPORT_FILE}" >> $GITHUB_OUTPUT
|
||||||
- name: Upload coverage results (to Codecov.io)
|
- name: Upload coverage results (to Codecov.io)
|
||||||
uses: codecov/codecov-action@v5
|
uses: codecov/codecov-action@v7
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.CODECOV_TOKEN }}
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
files: ${{ steps.coverage.outputs.report }}
|
files: ${{ steps.coverage.outputs.report }}
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ jobs:
|
|||||||
components: clippy
|
components: clippy
|
||||||
- uses: Swatinem/rust-cache@v2
|
- uses: Swatinem/rust-cache@v2
|
||||||
- name: Run sccache-cache
|
- name: Run sccache-cache
|
||||||
uses: mozilla-actions/sccache-action@v0.0.9
|
uses: mozilla-actions/sccache-action@v0.0.10
|
||||||
- name: Initialize workflow variables
|
- name: Initialize workflow variables
|
||||||
id: vars
|
id: vars
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|||||||
@@ -24,15 +24,17 @@ jobs:
|
|||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: dtolnay/rust-toolchain@nightly
|
|
||||||
- name: Install `cargo-fuzz`
|
- name: Install `cargo-fuzz`
|
||||||
run: cargo install cargo-fuzz
|
uses: taiki-e/install-action@v2
|
||||||
|
with:
|
||||||
|
tool: cargo-fuzz
|
||||||
- uses: Swatinem/rust-cache@v2
|
- uses: Swatinem/rust-cache@v2
|
||||||
with:
|
with:
|
||||||
shared-key: "cargo-fuzz-cache-key"
|
shared-key: "cargo-fuzz-cache-key"
|
||||||
cache-directories: "fuzz/target"
|
cache-directories: "fuzz/target"
|
||||||
- name: Run `cargo-fuzz build`
|
- 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:
|
fuzz-run:
|
||||||
needs: fuzz-build
|
needs: fuzz-build
|
||||||
@@ -50,9 +52,10 @@ jobs:
|
|||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: dtolnay/rust-toolchain@nightly
|
|
||||||
- name: Install `cargo-fuzz`
|
- name: Install `cargo-fuzz`
|
||||||
run: cargo install cargo-fuzz
|
uses: taiki-e/install-action@v2
|
||||||
|
with:
|
||||||
|
tool: cargo-fuzz
|
||||||
- uses: Swatinem/rust-cache@v2
|
- uses: Swatinem/rust-cache@v2
|
||||||
with:
|
with:
|
||||||
shared-key: "cargo-fuzz-cache-key"
|
shared-key: "cargo-fuzz-cache-key"
|
||||||
@@ -70,7 +73,8 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
mkdir -p fuzz/stats
|
mkdir -p fuzz/stats
|
||||||
STATS_FILE="fuzz/stats/${{ matrix.test-target.name }}.txt"
|
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
|
# Extract key stats from the output
|
||||||
if grep -q "stat::number_of_executed_units" "$STATS_FILE"; then
|
if grep -q "stat::number_of_executed_units" "$STATS_FILE"; then
|
||||||
@@ -146,7 +150,7 @@ jobs:
|
|||||||
path: |
|
path: |
|
||||||
fuzz/corpus/${{ matrix.test-target.name }}
|
fuzz/corpus/${{ matrix.test-target.name }}
|
||||||
- name: Upload Stats
|
- name: Upload Stats
|
||||||
uses: actions/upload-artifact@v6
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: fuzz-stats-${{ matrix.test-target.name }}
|
name: fuzz-stats-${{ matrix.test-target.name }}
|
||||||
path: |
|
path: |
|
||||||
@@ -163,7 +167,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Download all stats
|
- name: Download all stats
|
||||||
uses: actions/download-artifact@v7
|
uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
path: fuzz/stats-artifacts
|
path: fuzz/stats-artifacts
|
||||||
pattern: fuzz-stats-*
|
pattern: fuzz-stats-*
|
||||||
@@ -257,7 +261,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
cat fuzzing_summary.md
|
cat fuzzing_summary.md
|
||||||
- name: Upload Summary
|
- name: Upload Summary
|
||||||
uses: actions/upload-artifact@v6
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: fuzzing-summary
|
name: fuzzing-summary
|
||||||
path: fuzzing_summary.md
|
path: fuzzing_summary.md
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ jobs:
|
|||||||
shell: bash
|
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"
|
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
|
- name: Cache dist
|
||||||
uses: actions/upload-artifact@v6
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: cargo-dist-cache
|
name: cargo-dist-cache
|
||||||
path: ~/.cargo/bin/dist
|
path: ~/.cargo/bin/dist
|
||||||
@@ -82,7 +82,7 @@ jobs:
|
|||||||
cat plan-dist-manifest.json
|
cat plan-dist-manifest.json
|
||||||
echo "manifest=$(jq -c "." plan-dist-manifest.json)" >> "$GITHUB_OUTPUT"
|
echo "manifest=$(jq -c "." plan-dist-manifest.json)" >> "$GITHUB_OUTPUT"
|
||||||
- name: "Upload dist-manifest.json"
|
- name: "Upload dist-manifest.json"
|
||||||
uses: actions/upload-artifact@v6
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: artifacts-plan-dist-manifest
|
name: artifacts-plan-dist-manifest
|
||||||
path: plan-dist-manifest.json
|
path: plan-dist-manifest.json
|
||||||
@@ -131,7 +131,7 @@ jobs:
|
|||||||
run: ${{ matrix.install_dist.run }}
|
run: ${{ matrix.install_dist.run }}
|
||||||
# Get the dist-manifest
|
# Get the dist-manifest
|
||||||
- name: Fetch local artifacts
|
- name: Fetch local artifacts
|
||||||
uses: actions/download-artifact@v7
|
uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
pattern: artifacts-*
|
pattern: artifacts-*
|
||||||
path: target/distrib/
|
path: target/distrib/
|
||||||
@@ -158,7 +158,7 @@ jobs:
|
|||||||
|
|
||||||
cp dist-manifest.json "$BUILD_MANIFEST_NAME"
|
cp dist-manifest.json "$BUILD_MANIFEST_NAME"
|
||||||
- name: "Upload artifacts"
|
- name: "Upload artifacts"
|
||||||
uses: actions/upload-artifact@v6
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: artifacts-build-local-${{ join(matrix.targets, '_') }}
|
name: artifacts-build-local-${{ join(matrix.targets, '_') }}
|
||||||
path: |
|
path: |
|
||||||
@@ -180,14 +180,14 @@ jobs:
|
|||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
- name: Install cached dist
|
- name: Install cached dist
|
||||||
uses: actions/download-artifact@v7
|
uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: cargo-dist-cache
|
name: cargo-dist-cache
|
||||||
path: ~/.cargo/bin/
|
path: ~/.cargo/bin/
|
||||||
- run: chmod +x ~/.cargo/bin/dist
|
- run: chmod +x ~/.cargo/bin/dist
|
||||||
# Get all the local artifacts for the global tasks to use (for e.g. checksums)
|
# Get all the local artifacts for the global tasks to use (for e.g. checksums)
|
||||||
- name: Fetch local artifacts
|
- name: Fetch local artifacts
|
||||||
uses: actions/download-artifact@v7
|
uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
pattern: artifacts-*
|
pattern: artifacts-*
|
||||||
path: target/distrib/
|
path: target/distrib/
|
||||||
@@ -205,7 +205,7 @@ jobs:
|
|||||||
|
|
||||||
cp dist-manifest.json "$BUILD_MANIFEST_NAME"
|
cp dist-manifest.json "$BUILD_MANIFEST_NAME"
|
||||||
- name: "Upload artifacts"
|
- name: "Upload artifacts"
|
||||||
uses: actions/upload-artifact@v6
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: artifacts-build-global
|
name: artifacts-build-global
|
||||||
path: |
|
path: |
|
||||||
@@ -230,14 +230,14 @@ jobs:
|
|||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
- name: Install cached dist
|
- name: Install cached dist
|
||||||
uses: actions/download-artifact@v7
|
uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: cargo-dist-cache
|
name: cargo-dist-cache
|
||||||
path: ~/.cargo/bin/
|
path: ~/.cargo/bin/
|
||||||
- run: chmod +x ~/.cargo/bin/dist
|
- run: chmod +x ~/.cargo/bin/dist
|
||||||
# Fetch artifacts from scratch-storage
|
# Fetch artifacts from scratch-storage
|
||||||
- name: Fetch artifacts
|
- name: Fetch artifacts
|
||||||
uses: actions/download-artifact@v7
|
uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
pattern: artifacts-*
|
pattern: artifacts-*
|
||||||
path: target/distrib/
|
path: target/distrib/
|
||||||
@@ -250,14 +250,14 @@ jobs:
|
|||||||
cat dist-manifest.json
|
cat dist-manifest.json
|
||||||
echo "manifest=$(jq -c "." dist-manifest.json)" >> "$GITHUB_OUTPUT"
|
echo "manifest=$(jq -c "." dist-manifest.json)" >> "$GITHUB_OUTPUT"
|
||||||
- name: "Upload dist-manifest.json"
|
- name: "Upload dist-manifest.json"
|
||||||
uses: actions/upload-artifact@v6
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
# Overwrite the previous copy
|
# Overwrite the previous copy
|
||||||
name: artifacts-dist-manifest
|
name: artifacts-dist-manifest
|
||||||
path: dist-manifest.json
|
path: dist-manifest.json
|
||||||
# Create a GitHub Release while uploading all files to it
|
# Create a GitHub Release while uploading all files to it
|
||||||
- name: "Download GitHub Artifacts"
|
- name: "Download GitHub Artifacts"
|
||||||
uses: actions/download-artifact@v7
|
uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
pattern: artifacts-*
|
pattern: artifacts-*
|
||||||
path: artifacts
|
path: artifacts
|
||||||
|
|||||||
@@ -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@v6
|
||||||
|
- 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
+463
-356
File diff suppressed because it is too large
Load Diff
+49
-27
@@ -35,9 +35,9 @@ chrono = { version = "0.4.37", default-features = false, features = [
|
|||||||
] }
|
] }
|
||||||
clap = { version = "4.4", features = ["wrap_help", "cargo"] }
|
clap = { version = "4.4", features = ["wrap_help", "cargo"] }
|
||||||
clap_complete = "4.5"
|
clap_complete = "4.5"
|
||||||
clap_mangen = "0.2"
|
clap_mangen = "0.3"
|
||||||
divan = { package = "codspeed-divan-compat", version = "4.0.5" }
|
divan = { package = "codspeed-divan-compat", version = "4.0.5" }
|
||||||
fancy-regex = "0.17.0"
|
fancy-regex = "0.18.0"
|
||||||
hex = "0.4"
|
hex = "0.4"
|
||||||
libc = "0.2.153"
|
libc = "0.2.153"
|
||||||
memchr = "2.7.4"
|
memchr = "2.7.4"
|
||||||
@@ -45,23 +45,21 @@ memmap2 = "0.9"
|
|||||||
phf = "0.13.0"
|
phf = "0.13.0"
|
||||||
phf_codegen = "0.13.0"
|
phf_codegen = "0.13.0"
|
||||||
predicates = "3.1.3"
|
predicates = "3.1.3"
|
||||||
rand = { version = "0.9", features = ["small_rng"] }
|
rand = { version = "0.10.0" }
|
||||||
regex = "1.10.4"
|
regex = "1.10.4"
|
||||||
sha2 = "0.10"
|
sha2 = "0.11"
|
||||||
sysinfo = "0.38"
|
sysinfo = "0.38"
|
||||||
tempfile = "3.10.1"
|
tempfile = "3.10.1"
|
||||||
textwrap = { version = "0.16.1", features = ["terminal_size"] }
|
|
||||||
terminal_size = "0.4.2"
|
terminal_size = "0.4.2"
|
||||||
uucore = { version = "0.5.0", features = ["libc"] }
|
textwrap = { version = "0.16.1", features = ["terminal_size"] }
|
||||||
|
uucore = { version = "0.9.0", features = ["libc"] }
|
||||||
xattr = "1.3.1"
|
xattr = "1.3.1"
|
||||||
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
assert_fs = { workspace = true }
|
|
||||||
clap = { workspace = true }
|
clap = { workspace = true }
|
||||||
clap_complete = { workspace = true }
|
clap_complete = { workspace = true }
|
||||||
clap_mangen = { workspace = true }
|
clap_mangen = { workspace = true }
|
||||||
ctor = "0.6.0"
|
|
||||||
fancy-regex = { workspace = true }
|
fancy-regex = { workspace = true }
|
||||||
memchr = { workspace = true }
|
memchr = { workspace = true }
|
||||||
memmap2.workspace = true
|
memmap2.workspace = true
|
||||||
@@ -75,7 +73,9 @@ textwrap = { workspace = true }
|
|||||||
uucore = { workspace = true }
|
uucore = { workspace = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
assert_fs = { workspace = true }
|
||||||
chrono = { workspace = true }
|
chrono = { workspace = true }
|
||||||
|
ctor = "1.0.0"
|
||||||
divan = { workspace = true }
|
divan = { workspace = true }
|
||||||
hex = { workspace = true }
|
hex = { workspace = true }
|
||||||
libc = { workspace = true }
|
libc = { workspace = true }
|
||||||
@@ -85,13 +85,13 @@ regex = { workspace = true }
|
|||||||
sha2 = { workspace = true }
|
sha2 = { workspace = true }
|
||||||
tempfile = { workspace = true }
|
tempfile = { workspace = true }
|
||||||
uucore = { workspace = true, features = ["entries", "process", "signals", "benchmark"] }
|
uucore = { workspace = true, features = ["entries", "process", "signals", "benchmark"] }
|
||||||
uutests = "0.5.0"
|
uutests = "0.9.0"
|
||||||
|
|
||||||
[target.'cfg(unix)'.dev-dependencies]
|
[target.'cfg(unix)'.dev-dependencies]
|
||||||
xattr = { workspace = true }
|
xattr = { workspace = true }
|
||||||
|
|
||||||
[target.'cfg(any(target_os = "linux", target_os = "android"))'.dev-dependencies]
|
[target.'cfg(any(target_os = "linux", target_os = "android"))'.dev-dependencies]
|
||||||
rlimit = "0.10.1"
|
rlimit = "0.11.0"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
phf_codegen = { workspace = true }
|
phf_codegen = { workspace = true }
|
||||||
@@ -107,31 +107,53 @@ path = "src/bin/sed.rs"
|
|||||||
name = "sed_bench"
|
name = "sed_bench"
|
||||||
harness = false
|
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]
|
[profile.release]
|
||||||
lto = true
|
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"
|
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.
|
# A release-like profile that is as small as possible.
|
||||||
[profile.release-small]
|
[profile.release-small]
|
||||||
inherits = "release-fast"
|
inherits = "release"
|
||||||
opt-level = "z"
|
opt-level = "z"
|
||||||
strip = true
|
strip = true
|
||||||
|
|
||||||
[lints.clippy]
|
[lints.clippy]
|
||||||
default_trait_access = "warn"
|
all = { level = "warn", priority = -1 }
|
||||||
manual_string_new = "warn"
|
|
||||||
|
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"
|
cognitive_complexity = "warn"
|
||||||
implicit_clone = "warn"
|
redundant_clone = "warn"
|
||||||
range-plus-one = "warn"
|
|
||||||
redundant-clone = "warn"
|
|
||||||
match_bool = "warn"
|
|
||||||
semicolon_if_nothing_returned = "warn"
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
[](https://github.com/uutils/sed/blob/main/LICENSE)
|
[](https://github.com/uutils/sed/blob/main/LICENSE)
|
||||||
[](https://deps.rs/repo/github/uutils/sed)
|
[](https://deps.rs/repo/github/uutils/sed)
|
||||||
|
|
||||||
[](https://codecov.io/gh/uutils/sed)
|
[](https://codecov.io/gh/uutils/sed)
|
||||||
|
|
||||||
# sed
|
# sed
|
||||||
|
|
||||||
@@ -14,7 +14,7 @@ and other extensions.
|
|||||||
|
|
||||||
## Status
|
## 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:
|
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
|
[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).
|
[math.sed](https://github.com/uutils/sed/blob/main/tests/fixtures/sed/script/math.sed) (implements an arbitrary precision integer math calculator).
|
||||||
@@ -28,7 +28,10 @@ Further work aims to:
|
|||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
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:
|
Clone the repository and build the project using Cargo:
|
||||||
|
|
||||||
@@ -45,7 +48,8 @@ The binary is named `sed` in `target/release/sed`.
|
|||||||
|
|
||||||
### GNU sed Compatibility 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
|
```bash
|
||||||
# Clone GNU sed testsuite (one time setup)
|
# Clone GNU sed testsuite (one time setup)
|
||||||
@@ -54,11 +58,16 @@ git clone https://github.com/mirror/sed.git ../gnu.sed
|
|||||||
# Run compatibility tests
|
# Run compatibility tests
|
||||||
./util/run-gnu-testsuite.sh
|
./util/run-gnu-testsuite.sh
|
||||||
|
|
||||||
|
# Verbose mode shows failure details
|
||||||
|
./util/run-gnu-testsuite.sh -v
|
||||||
|
|
||||||
# Generate JSON results for CI
|
# Generate JSON results for CI
|
||||||
./util/run-gnu-testsuite.sh --json-output results.json
|
./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
|
### Unit Tests
|
||||||
|
|
||||||
@@ -78,6 +87,7 @@ cargo test
|
|||||||
* The `a`, `c`, and `i` commands do not require an initial backslash,
|
* The `a`, `c`, and `i` commands do not require an initial backslash,
|
||||||
allow text to appear on the same line, and support escape sequences
|
allow text to appear on the same line, and support escape sequences
|
||||||
in the specified text.
|
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 &.
|
* The substitution command replacement group `\0` is a synonym for &.
|
||||||
* A `Q` command (optionally followed by an exit code) quits immediately.
|
* A `Q` command (optionally followed by an exit code) quits immediately.
|
||||||
* The `q` command can be optionally followed by an exit code.
|
* The `q` command can be optionally followed by an exit code.
|
||||||
|
|||||||
+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
+488
-481
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"
|
libfuzzer-sys = "0.4.7"
|
||||||
libc = "0.2.153"
|
libc = "0.2.153"
|
||||||
tempfile = "3.15.0"
|
tempfile = "3.15.0"
|
||||||
rand = { version = "0.9.0", features = ["small_rng"] }
|
rand = { version = "0.10.0" }
|
||||||
similar = "2.5.0"
|
similar = "3.0.0"
|
||||||
uucore = { version = "0.5.0", features = ["libc"] }
|
uucore = { version = "0.9.0", features = ["libc"] }
|
||||||
uufuzz = "0.5.0"
|
uufuzz = "0.9.0"
|
||||||
|
|
||||||
sed = { path = ".." }
|
sed = { path = ".." }
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -162,12 +162,12 @@ impl ReplacementTemplate {
|
|||||||
ReplacementPart::Literal(s) => result.push_str(s),
|
ReplacementPart::Literal(s) => result.push_str(s),
|
||||||
|
|
||||||
ReplacementPart::WholeMatch => {
|
ReplacementPart::WholeMatch => {
|
||||||
result.push_str(caps.get(0)?.map(|m| m.as_str()).unwrap_or(""));
|
result.push_str(caps.get(0)?.map(|m| m.as_str()).unwrap_or_default());
|
||||||
}
|
}
|
||||||
|
|
||||||
ReplacementPart::Group(n) => {
|
ReplacementPart::Group(n) => {
|
||||||
let i: usize = (*n).try_into().unwrap();
|
let i: usize = (*n).try_into().unwrap();
|
||||||
result.push_str(caps.get(i)?.map(|m| m.as_str()).unwrap_or(""));
|
result.push_str(caps.get(i)?.map(|m| m.as_str()).unwrap_or_default());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+98
-49
@@ -120,7 +120,7 @@ fn patch_block_endings(head: Option<Rc<RefCell<Command>>>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 3) splice the tail’s `.next` to splice_target
|
// 3) splice the tail’s `.next` to splice_target
|
||||||
tail.borrow_mut().next = splice_target.clone();
|
tail.borrow_mut().next.clone_from(&splice_target);
|
||||||
}
|
}
|
||||||
|
|
||||||
// drop the borrow before moving on
|
// drop the borrow before moving on
|
||||||
@@ -140,7 +140,7 @@ fn populate_label_map(
|
|||||||
mut cur: Option<Rc<RefCell<Command>>>,
|
mut cur: Option<Rc<RefCell<Command>>>,
|
||||||
context: &mut ProcessingContext,
|
context: &mut ProcessingContext,
|
||||||
) -> UResult<()> {
|
) -> UResult<()> {
|
||||||
while let Some(rc_cmd) = cur {
|
while let Some(rc_cmd) = cur.take() {
|
||||||
// Borrow mutably just long enough to inspect/rewire this node
|
// Borrow mutably just long enough to inspect/rewire this node
|
||||||
let cmd = rc_cmd.borrow_mut();
|
let cmd = rc_cmd.borrow_mut();
|
||||||
|
|
||||||
@@ -163,14 +163,14 @@ fn populate_label_map(
|
|||||||
context.label_to_command_map.insert(label, rc_cmd.clone());
|
context.label_to_command_map.insert(label, rc_cmd.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
cur = cmd.next.clone();
|
cur.clone_from(&cmd.next);
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Populate the context's address range command list with references to associated commands.
|
/// Populate the context's address range command list with references to associated commands.
|
||||||
fn populate_range_commands(mut cur: Option<Rc<RefCell<Command>>>, context: &mut ProcessingContext) {
|
fn populate_range_commands(mut cur: Option<Rc<RefCell<Command>>>, context: &mut ProcessingContext) {
|
||||||
while let Some(rc_cmd) = cur {
|
while let Some(rc_cmd) = cur.take() {
|
||||||
// Borrow mutably just long enough to inspect/rewire this node
|
// Borrow mutably just long enough to inspect/rewire this node
|
||||||
let cmd = rc_cmd.borrow_mut();
|
let cmd = rc_cmd.borrow_mut();
|
||||||
|
|
||||||
@@ -184,7 +184,7 @@ fn populate_range_commands(mut cur: Option<Rc<RefCell<Command>>>, context: &mut
|
|||||||
context.range_commands.push(Rc::clone(&rc_cmd));
|
context.range_commands.push(Rc::clone(&rc_cmd));
|
||||||
}
|
}
|
||||||
|
|
||||||
cur = cmd.next.clone();
|
cur.clone_from(&cmd.next);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,7 +194,7 @@ fn resolve_branch_targets(
|
|||||||
mut cur: Option<Rc<RefCell<Command>>>,
|
mut cur: Option<Rc<RefCell<Command>>>,
|
||||||
context: &mut ProcessingContext,
|
context: &mut ProcessingContext,
|
||||||
) -> UResult<()> {
|
) -> UResult<()> {
|
||||||
while let Some(rc_cmd) = cur {
|
while let Some(rc_cmd) = cur.take() {
|
||||||
// Borrow mutably just long enough to inspect/rewire this node
|
// Borrow mutably just long enough to inspect/rewire this node
|
||||||
let mut cmd = rc_cmd.borrow_mut();
|
let mut cmd = rc_cmd.borrow_mut();
|
||||||
|
|
||||||
@@ -233,7 +233,7 @@ fn resolve_branch_targets(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Advance to the next sibling
|
// Advance to the next sibling
|
||||||
cur = cmd.next.clone();
|
cur.clone_from(&cmd.next);
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -285,14 +285,14 @@ fn compile_sequence(
|
|||||||
let mut cmd = Rc::new(RefCell::new(Command::at_position(lines, line)));
|
let mut cmd = Rc::new(RefCell::new(Command::at_position(lines, line)));
|
||||||
let n_addr = compile_address_range(lines, line, &mut cmd, context)?;
|
let n_addr = compile_address_range(lines, line, &mut cmd, context)?;
|
||||||
line.eat_spaces();
|
line.eat_spaces();
|
||||||
let mut cmd_spec = get_verified_cmd_spec(lines, line, n_addr)?;
|
let mut cmd_spec = get_verified_cmd_spec(lines, line, n_addr, context.posix)?;
|
||||||
|
|
||||||
// Compile the command according to its specification.
|
// Compile the command according to its specification.
|
||||||
let mut cmd_mut = cmd.borrow_mut();
|
let mut cmd_mut = cmd.borrow_mut();
|
||||||
cmd_mut.code = line.current();
|
cmd_mut.code = line.current();
|
||||||
match (cmd_spec.handler)(lines, line, &mut cmd_mut, context)? {
|
match (cmd_spec.handler)(lines, line, &mut cmd_mut, context)? {
|
||||||
CommandHandling::GetNext => {
|
CommandHandling::GetNext => {
|
||||||
cmd_spec = get_verified_cmd_spec(lines, line, n_addr)?;
|
cmd_spec = get_verified_cmd_spec(lines, line, n_addr, context.posix)?;
|
||||||
cmd_mut.code = line.current();
|
cmd_mut.code = line.current();
|
||||||
(cmd_spec.handler)(lines, line, &mut cmd_mut, context)?;
|
(cmd_spec.handler)(lines, line, &mut cmd_mut, context)?;
|
||||||
}
|
}
|
||||||
@@ -498,9 +498,8 @@ fn parse_number(
|
|||||||
if num_str.is_empty() {
|
if num_str.is_empty() {
|
||||||
if required {
|
if required {
|
||||||
return compilation_error(lines, line, "number expected");
|
return compilation_error(lines, line, "number expected");
|
||||||
} else {
|
|
||||||
return Ok(None);
|
|
||||||
}
|
}
|
||||||
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
num_str
|
num_str
|
||||||
@@ -533,7 +532,7 @@ fn parse_command_ending(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Convert a primitive BRE pattern to a safe ERE-compatible pattern string.
|
/// Convert a primitive BRE pattern to a safe ERE-compatible pattern string.
|
||||||
/// - Replaces `\(` and `\)` with `(` and `)`.
|
/// - Replaces `\(`, `\)`, `\?`, `\+` and `\|` with `(`, `)`, `?`, `+` and `|`.
|
||||||
/// - Puts single-digit back-references in non-capturing groups..
|
/// - Puts single-digit back-references in non-capturing groups..
|
||||||
/// - Escapes ERE-only metacharacters: `+ ? { } | ( )`.
|
/// - Escapes ERE-only metacharacters: `+ ? { } | ( )`.
|
||||||
/// - Leaves all other characters as-is.
|
/// - Leaves all other characters as-is.
|
||||||
@@ -554,6 +553,18 @@ fn bre_to_ere(pattern: &str) -> String {
|
|||||||
chars.next();
|
chars.next();
|
||||||
result.push(')'); // Group end
|
result.push(')'); // Group end
|
||||||
}
|
}
|
||||||
|
Some('?') => {
|
||||||
|
chars.next();
|
||||||
|
result.push('?'); // Quantifier 0 or 1
|
||||||
|
}
|
||||||
|
Some('+') => {
|
||||||
|
chars.next();
|
||||||
|
result.push('+'); // Quantifier 1 or more
|
||||||
|
}
|
||||||
|
Some('|') => {
|
||||||
|
chars.next();
|
||||||
|
result.push('|'); // Alternation operator
|
||||||
|
}
|
||||||
Some(v) if v.is_ascii_digit() => {
|
Some(v) if v.is_ascii_digit() => {
|
||||||
// Back-reference. In sed BREs these are single-digit
|
// Back-reference. In sed BREs these are single-digit
|
||||||
// (\1-\9) whereas fancy_regex supports multi-digit
|
// (\1-\9) whereas fancy_regex supports multi-digit
|
||||||
@@ -668,14 +679,13 @@ pub fn compile_replacement(
|
|||||||
literal.push('\n');
|
literal.push('\n');
|
||||||
*line = ScriptCharProvider::new(&next_line_string);
|
*line = ScriptCharProvider::new(&next_line_string);
|
||||||
continue;
|
continue;
|
||||||
} else {
|
}
|
||||||
return compilation_error(
|
return compilation_error(
|
||||||
lines,
|
lines,
|
||||||
line,
|
line,
|
||||||
"unterminated substitute replacement (unexpected EOF)",
|
"unterminated substitute replacement (unexpected EOF)",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
match line.current() {
|
match line.current() {
|
||||||
// \0 - \9
|
// \0 - \9
|
||||||
@@ -706,14 +716,15 @@ pub fn compile_replacement(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// other escape sequences
|
// other escape sequences
|
||||||
_ => match parse_char_escape(line) {
|
_ => {
|
||||||
Some(decoded) => literal.push(decoded),
|
if let Some(decoded) = parse_char_escape(line) {
|
||||||
None => {
|
literal.push(decoded);
|
||||||
|
} else {
|
||||||
literal.push('\\');
|
literal.push('\\');
|
||||||
literal.push(line.current());
|
literal.push(line.current());
|
||||||
line.advance();
|
line.advance();
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1118,7 +1129,6 @@ fn compile_text_command(
|
|||||||
context: &mut ProcessingContext,
|
context: &mut ProcessingContext,
|
||||||
) -> UResult<CommandHandling> {
|
) -> UResult<CommandHandling> {
|
||||||
line.advance(); // Skip the command character.
|
line.advance(); // Skip the command character.
|
||||||
|
|
||||||
line.eat_spaces(); // Skip any leading whitespace.
|
line.eat_spaces(); // Skip any leading whitespace.
|
||||||
if context.posix {
|
if context.posix {
|
||||||
compile_text_command_posix(lines, line, cmd, context)
|
compile_text_command_posix(lines, line, cmd, context)
|
||||||
@@ -1141,6 +1151,14 @@ fn compile_text_command_gnu(
|
|||||||
// True after a \ at the end of a line
|
// True after a \ at the end of a line
|
||||||
let mut escaped_newline = false;
|
let mut escaped_newline = false;
|
||||||
|
|
||||||
|
if line.eol() {
|
||||||
|
return compilation_error(
|
||||||
|
lines,
|
||||||
|
line,
|
||||||
|
format!("command `{}' expects \\ followed by text", cmd.code),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Skip optional \.
|
// Skip optional \.
|
||||||
if !line.eol() && line.current() == '\\' {
|
if !line.eol() && line.current() == '\\' {
|
||||||
line.advance();
|
line.advance();
|
||||||
@@ -1177,14 +1195,13 @@ fn compile_text_command_gnu(
|
|||||||
continue 'text_content;
|
continue 'text_content;
|
||||||
}
|
}
|
||||||
|
|
||||||
match parse_char_escape(line) {
|
if let Some(decoded) = parse_char_escape(line) {
|
||||||
Some(decoded) => text.push(decoded),
|
text.push(decoded);
|
||||||
None => {
|
} else {
|
||||||
// Invalid escapes result in the escaped character.
|
// Invalid escapes result in the escaped character.
|
||||||
text.push(line.current());
|
text.push(line.current());
|
||||||
line.advance();
|
line.advance();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
text.push(line.current());
|
text.push(line.current());
|
||||||
line.advance();
|
line.advance();
|
||||||
@@ -1236,6 +1253,11 @@ fn compile_text_command_posix(
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if text.is_empty() {
|
||||||
|
compilation_error(lines, line, "incomplete command")?;
|
||||||
|
}
|
||||||
|
|
||||||
cmd.data = CommandData::Text(Rc::from(text));
|
cmd.data = CommandData::Text(Rc::from(text));
|
||||||
Ok(CommandHandling::Continue)
|
Ok(CommandHandling::Continue)
|
||||||
}
|
}
|
||||||
@@ -1246,13 +1268,14 @@ fn get_verified_cmd_spec(
|
|||||||
lines: &ScriptLineProvider,
|
lines: &ScriptLineProvider,
|
||||||
line: &ScriptCharProvider,
|
line: &ScriptCharProvider,
|
||||||
n_addr: usize,
|
n_addr: usize,
|
||||||
|
posix: bool,
|
||||||
) -> UResult<CommandSpec> {
|
) -> UResult<CommandSpec> {
|
||||||
if line.eol() {
|
if line.eol() {
|
||||||
return compilation_error(lines, line, "command expected");
|
return compilation_error(lines, line, "command expected");
|
||||||
}
|
}
|
||||||
|
|
||||||
let ch = line.current();
|
let ch = line.current();
|
||||||
let cmd_spec = get_cmd_spec(lines, line, ch)?;
|
let cmd_spec = get_cmd_spec(lines, line, ch, posix)?;
|
||||||
|
|
||||||
if n_addr > cmd_spec.n_addr {
|
if n_addr > cmd_spec.n_addr {
|
||||||
return compilation_error(
|
return compilation_error(
|
||||||
@@ -1273,6 +1296,7 @@ fn get_cmd_spec(
|
|||||||
lines: &ScriptLineProvider,
|
lines: &ScriptLineProvider,
|
||||||
line: &ScriptCharProvider,
|
line: &ScriptCharProvider,
|
||||||
cmd_code: char,
|
cmd_code: char,
|
||||||
|
posix: bool,
|
||||||
) -> UResult<CommandSpec> {
|
) -> UResult<CommandSpec> {
|
||||||
match cmd_code {
|
match cmd_code {
|
||||||
'!' => Ok(CommandSpec {
|
'!' => Ok(CommandSpec {
|
||||||
@@ -1280,7 +1304,7 @@ fn get_cmd_spec(
|
|||||||
handler: compile_negation_command,
|
handler: compile_negation_command,
|
||||||
}),
|
}),
|
||||||
'=' => Ok(CommandSpec {
|
'=' => Ok(CommandSpec {
|
||||||
n_addr: 1,
|
n_addr: if posix { 1 } else { 2 },
|
||||||
handler: compile_empty_command,
|
handler: compile_empty_command,
|
||||||
}),
|
}),
|
||||||
':' => Ok(CommandSpec {
|
':' => Ok(CommandSpec {
|
||||||
@@ -1296,7 +1320,7 @@ fn get_cmd_spec(
|
|||||||
handler: compile_end_group_command,
|
handler: compile_end_group_command,
|
||||||
}),
|
}),
|
||||||
'a' | 'i' => Ok(CommandSpec {
|
'a' | 'i' => Ok(CommandSpec {
|
||||||
n_addr: 1,
|
n_addr: if posix { 1 } else { 2 },
|
||||||
handler: compile_text_command,
|
handler: compile_text_command,
|
||||||
}),
|
}),
|
||||||
'b' | 't' => Ok(CommandSpec {
|
'b' | 't' => Ok(CommandSpec {
|
||||||
@@ -1315,13 +1339,17 @@ fn get_cmd_spec(
|
|||||||
n_addr: 2,
|
n_addr: 2,
|
||||||
handler: compile_number_command,
|
handler: compile_number_command,
|
||||||
}),
|
}),
|
||||||
|
'q' => Ok(CommandSpec {
|
||||||
|
n_addr: if posix { 1 } else { 2 },
|
||||||
|
handler: compile_number_command,
|
||||||
|
}),
|
||||||
// Q is a GNU extension
|
// Q is a GNU extension
|
||||||
'q' | 'Q' => Ok(CommandSpec {
|
'Q' => Ok(CommandSpec {
|
||||||
n_addr: 1,
|
n_addr: 1,
|
||||||
handler: compile_number_command,
|
handler: compile_number_command,
|
||||||
}),
|
}),
|
||||||
'r' => Ok(CommandSpec {
|
'r' => Ok(CommandSpec {
|
||||||
n_addr: 1,
|
n_addr: if posix { 1 } else { 2 },
|
||||||
handler: compile_read_file_command,
|
handler: compile_read_file_command,
|
||||||
}),
|
}),
|
||||||
's' => Ok(CommandSpec {
|
's' => Ok(CommandSpec {
|
||||||
@@ -1373,35 +1401,35 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_lookup_empty_command() {
|
fn test_lookup_empty_command() {
|
||||||
let (lines, line) = make_providers("123abc");
|
let (lines, line) = make_providers("123abc");
|
||||||
let cmd = get_cmd_spec(&lines, &line, 'd').unwrap();
|
let cmd = get_cmd_spec(&lines, &line, 'd', false).unwrap();
|
||||||
assert_eq!(cmd.n_addr, 2);
|
assert_eq!(cmd.n_addr, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_lookup_text_command() {
|
fn test_lookup_text_command() {
|
||||||
let (lines, line) = make_providers("123abc");
|
let (lines, line) = make_providers("123abc");
|
||||||
let cmd = get_cmd_spec(&lines, &line, 'a').unwrap();
|
let cmd = get_cmd_spec(&lines, &line, 'a', false).unwrap();
|
||||||
assert_eq!(cmd.n_addr, 1);
|
assert_eq!(cmd.n_addr, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_lookup_nonselect_command() {
|
fn test_lookup_nonselect_command() {
|
||||||
let (lines, line) = make_providers("123abc");
|
let (lines, line) = make_providers("123abc");
|
||||||
let cmd = get_cmd_spec(&lines, &line, '!').unwrap();
|
let cmd = get_cmd_spec(&lines, &line, '!', false).unwrap();
|
||||||
assert_eq!(cmd.n_addr, 2);
|
assert_eq!(cmd.n_addr, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_lookup_endgroup_command() {
|
fn test_lookup_endgroup_command() {
|
||||||
let (lines, line) = make_providers("123abc");
|
let (lines, line) = make_providers("123abc");
|
||||||
let cmd = get_cmd_spec(&lines, &line, '}').unwrap();
|
let cmd = get_cmd_spec(&lines, &line, '}', false).unwrap();
|
||||||
assert_eq!(cmd.n_addr, 0);
|
assert_eq!(cmd.n_addr, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_lookup_invalid_command() {
|
fn test_lookup_invalid_command() {
|
||||||
let (lines, line) = make_providers("123abc");
|
let (lines, line) = make_providers("123abc");
|
||||||
let result = get_cmd_spec(&lines, &line, 'Z');
|
let result = get_cmd_spec(&lines, &line, 'Z', false);
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1453,7 +1481,7 @@ mod tests {
|
|||||||
fn test_missing_command_character() {
|
fn test_missing_command_character() {
|
||||||
let lines = ScriptLineProvider::with_active_state("test.sed", 1);
|
let lines = ScriptLineProvider::with_active_state("test.sed", 1);
|
||||||
let line = char_provider_from("");
|
let line = char_provider_from("");
|
||||||
let result = get_verified_cmd_spec(&lines, &line, 0);
|
let result = get_verified_cmd_spec(&lines, &line, 0, ctx().posix);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
let msg = result.unwrap_err().to_string();
|
let msg = result.unwrap_err().to_string();
|
||||||
@@ -1464,7 +1492,7 @@ mod tests {
|
|||||||
fn test_invalid_command_character() {
|
fn test_invalid_command_character() {
|
||||||
let lines = ScriptLineProvider::with_active_state("script.sed", 2);
|
let lines = ScriptLineProvider::with_active_state("script.sed", 2);
|
||||||
let line = char_provider_from("@");
|
let line = char_provider_from("@");
|
||||||
let result = get_verified_cmd_spec(&lines, &line, 0);
|
let result = get_verified_cmd_spec(&lines, &line, 0, ctx().posix);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
let msg = result.unwrap_err().to_string();
|
let msg = result.unwrap_err().to_string();
|
||||||
@@ -1475,7 +1503,7 @@ mod tests {
|
|||||||
fn test_too_many_addresses() {
|
fn test_too_many_addresses() {
|
||||||
let lines = ScriptLineProvider::with_active_state("input.sed", 3);
|
let lines = ScriptLineProvider::with_active_state("input.sed", 3);
|
||||||
let line = char_provider_from("q"); // q takes one address
|
let line = char_provider_from("q"); // q takes one address
|
||||||
let result = get_verified_cmd_spec(&lines, &line, 2);
|
let result = get_verified_cmd_spec(&lines, &line, 2, true);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
let msg = result.unwrap_err().to_string();
|
let msg = result.unwrap_err().to_string();
|
||||||
@@ -1488,11 +1516,22 @@ mod tests {
|
|||||||
fn test_valid_command_spec() {
|
fn test_valid_command_spec() {
|
||||||
let lines = ScriptLineProvider::with_active_state("input.sed", 4);
|
let lines = ScriptLineProvider::with_active_state("input.sed", 4);
|
||||||
let line = char_provider_from("a"); // valid command
|
let line = char_provider_from("a"); // valid command
|
||||||
let result = get_verified_cmd_spec(&lines, &line, 1);
|
let result = get_verified_cmd_spec(&lines, &line, 2, ctx().posix);
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
let spec = result.unwrap();
|
let spec = result.unwrap();
|
||||||
assert_eq!(spec.n_addr, 1);
|
assert_eq!(spec.n_addr, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_invalid_address_range_posix() {
|
||||||
|
let lines = ScriptLineProvider::with_active_state("input.sed", 1);
|
||||||
|
let line = char_provider_from("i"); // valid command
|
||||||
|
let result = get_verified_cmd_spec(&lines, &line, 2, true);
|
||||||
|
assert!(result.is_err());
|
||||||
|
let msg = result.unwrap_err().to_string();
|
||||||
|
assert!(
|
||||||
|
msg.contains("input.sed:1:1: error: command i expects up to 1 address(es), found 2")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse_number
|
// parse_number
|
||||||
@@ -2171,7 +2210,7 @@ mod tests {
|
|||||||
// bre_to_ere
|
// bre_to_ere
|
||||||
#[test]
|
#[test]
|
||||||
fn test_bre_group_translation() {
|
fn test_bre_group_translation() {
|
||||||
assert_eq!(bre_to_ere(r"\(abc\)"), "(abc)");
|
assert_eq!(bre_to_ere(r"\(a\?b\+c\|\)"), "(a?b+c|)");
|
||||||
assert_eq!(bre_to_ere(r"a\(b\)c"), "a(b)c");
|
assert_eq!(bre_to_ere(r"a\(b\)c"), "a(b)c");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2720,6 +2759,19 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_compile_text_command_posix_incomplete() {
|
||||||
|
let (mut lines, mut chars) = make_providers("i\\");
|
||||||
|
let mut cmd = Command::default();
|
||||||
|
let mut context = ProcessingContext {
|
||||||
|
posix: true,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let result = compile_text_command(&mut lines, &mut chars, &mut cmd, &mut context);
|
||||||
|
let err = result.unwrap_err().to_string();
|
||||||
|
assert!(err.contains("incomplete command"));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_compile_text_command_gnu_optional_backslash() {
|
fn test_compile_text_command_gnu_optional_backslash() {
|
||||||
let mut chars = make_char_provider("athere");
|
let mut chars = make_char_provider("athere");
|
||||||
@@ -2753,19 +2805,16 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_compile_text_command_gnu_optional_backslash_eol_eof() {
|
fn test_compile_text_command_gnu_no_text() {
|
||||||
let mut chars = make_char_provider("a");
|
let mut chars = make_char_provider("a");
|
||||||
let mut lines = make_line_provider(&[]);
|
let mut lines = make_line_provider(&[]);
|
||||||
let mut cmd = Command::default();
|
let mut cmd = Command::default();
|
||||||
let mut context = ProcessingContext::default();
|
let mut context = ProcessingContext::default();
|
||||||
|
|
||||||
compile_text_command(&mut lines, &mut chars, &mut cmd, &mut context).unwrap();
|
let result = compile_text_command(&mut lines, &mut chars, &mut cmd, &mut context);
|
||||||
match &cmd.data {
|
assert!(result.is_err());
|
||||||
CommandData::Text(text) => {
|
let err = result.unwrap_err().to_string();
|
||||||
assert_eq!(text.to_string(), "\n");
|
assert!(err.contains("expects \\ followed by text"));
|
||||||
}
|
|
||||||
_ => panic!("Expected CommandData::Text"),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
+20
-25
@@ -77,7 +77,7 @@ fn create_control_char(x: char) -> Option<char> {
|
|||||||
let c = x.to_ascii_uppercase();
|
let c = x.to_ascii_uppercase();
|
||||||
|
|
||||||
let transformed = (c as u8) ^ 0x40;
|
let transformed = (c as u8) ^ 0x40;
|
||||||
char::from_u32(transformed as u32)
|
char::from_u32(u32::from(transformed))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a character escape valid in all contexts (RE pattern, substitution,
|
/// Parse a character escape valid in all contexts (RE pattern, substitution,
|
||||||
@@ -186,9 +186,10 @@ fn parse_character_class(
|
|||||||
) -> UResult<String> {
|
) -> UResult<String> {
|
||||||
let mut result = String::new();
|
let mut result = String::new();
|
||||||
|
|
||||||
if line.eol() || line.current() != '[' {
|
assert!(
|
||||||
panic!("Invalid character class.");
|
!line.eol() && line.current() == '[',
|
||||||
}
|
"Invalid character class."
|
||||||
|
);
|
||||||
|
|
||||||
line.advance();
|
line.advance();
|
||||||
result.push('[');
|
result.push('[');
|
||||||
@@ -216,7 +217,10 @@ fn parse_character_class(
|
|||||||
|
|
||||||
if ch == '[' {
|
if ch == '[' {
|
||||||
line.advance();
|
line.advance();
|
||||||
if !line.eol() {
|
if line.eol() {
|
||||||
|
result.push('[');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
let marker = line.current();
|
let marker = line.current();
|
||||||
// POSIX character class, collating symbol, or equivalence
|
// POSIX character class, collating symbol, or equivalence
|
||||||
if marker == ':' || marker == '.' || marker == '=' {
|
if marker == ':' || marker == '.' || marker == '=' {
|
||||||
@@ -239,10 +243,9 @@ fn parse_character_class(
|
|||||||
result.push(']');
|
result.push(']');
|
||||||
terminated = true;
|
terminated = true;
|
||||||
break;
|
break;
|
||||||
} else {
|
}
|
||||||
// False alarm, just part of the inner name
|
// False alarm, just part of the inner name
|
||||||
inner.push(marker);
|
inner.push(marker);
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
inner.push(c);
|
inner.push(c);
|
||||||
line.advance();
|
line.advance();
|
||||||
@@ -258,18 +261,13 @@ fn parse_character_class(
|
|||||||
}
|
}
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
} else {
|
}
|
||||||
// Not a POSIX construct — treat as literal
|
// Not a POSIX construct — treat as literal
|
||||||
result.push('[');
|
result.push('[');
|
||||||
result.push(marker);
|
result.push(marker);
|
||||||
line.advance();
|
line.advance();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
result.push('[');
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ch == '\\' {
|
if ch == '\\' {
|
||||||
// Handle escape sequence
|
// Handle escape sequence
|
||||||
@@ -277,14 +275,13 @@ fn parse_character_class(
|
|||||||
if line.eol() {
|
if line.eol() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
match parse_char_escape(line) {
|
if let Some(decoded) = parse_char_escape(line) {
|
||||||
Some(decoded) => result.push(decoded),
|
result.push(decoded);
|
||||||
None => {
|
} else {
|
||||||
result.push('\\');
|
result.push('\\');
|
||||||
result.push(line.current());
|
result.push(line.current());
|
||||||
line.advance();
|
line.advance();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
result.push(ch);
|
result.push(ch);
|
||||||
line.advance();
|
line.advance();
|
||||||
@@ -335,15 +332,14 @@ pub fn parse_regex(lines: &ScriptLineProvider, line: &mut ScriptCharProvider) ->
|
|||||||
line.advance();
|
line.advance();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
match parse_char_escape(line) {
|
if let Some(decoded) = parse_char_escape(line) {
|
||||||
Some(decoded) => result.push(decoded),
|
result.push(decoded);
|
||||||
None => {
|
} else {
|
||||||
// Pass through \<any> to RE engine for further treatment
|
// Pass through \<any> to RE engine for further treatment
|
||||||
result.push('\\');
|
result.push('\\');
|
||||||
result.push(line.current());
|
result.push(line.current());
|
||||||
line.advance();
|
line.advance();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
c if c == delimiter => return Ok(result),
|
c if c == delimiter => return Ok(result),
|
||||||
@@ -377,15 +373,14 @@ pub fn parse_transliteration(
|
|||||||
line.advance();
|
line.advance();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
match parse_char_escape(line) {
|
if let Some(decoded) = parse_char_escape(line) {
|
||||||
Some(decoded) => result.push(decoded),
|
result.push(decoded);
|
||||||
None => {
|
} else {
|
||||||
// Pass through \<any> to tr for literal use
|
// Pass through \<any> to tr for literal use
|
||||||
result.push('\\');
|
result.push('\\');
|
||||||
result.push(line.current());
|
result.push(line.current());
|
||||||
line.advance();
|
line.advance();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
c if c == delimiter => return Ok(result),
|
c if c == delimiter => return Ok(result),
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user