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'
|
||||
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');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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: |
|
||||
@@ -132,7 +134,7 @@ jobs:
|
||||
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@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: rustsec/audit-check@v2
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -29,21 +29,16 @@ jobs:
|
||||
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.10
|
||||
|
||||
- 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
|
||||
|
||||
@@ -17,6 +17,27 @@ jobs:
|
||||
- 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@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:
|
||||
name: cargo test
|
||||
runs-on: ${{ matrix.os }}
|
||||
@@ -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 }}
|
||||
|
||||
@@ -51,7 +51,7 @@ jobs:
|
||||
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.10
|
||||
- name: Initialize workflow variables
|
||||
id: vars
|
||||
shell: bash
|
||||
|
||||
@@ -24,15 +24,17 @@ jobs:
|
||||
- uses: actions/checkout@v6
|
||||
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
|
||||
@@ -50,9 +52,10 @@ jobs:
|
||||
- uses: actions/checkout@v6
|
||||
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"
|
||||
@@ -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
|
||||
@@ -146,7 +150,7 @@ jobs:
|
||||
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: |
|
||||
@@ -163,7 +167,7 @@ jobs:
|
||||
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
|
||||
|
||||
@@ -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
|
||||
@@ -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: |
|
||||
@@ -180,14 +180,14 @@ jobs:
|
||||
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: |
|
||||
@@ -230,14 +230,14 @@ jobs:
|
||||
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
|
||||
|
||||
@@ -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_complete = "4.5"
|
||||
clap_mangen = "0.2"
|
||||
clap_mangen = "0.3"
|
||||
divan = { package = "codspeed-divan-compat", version = "4.0.5" }
|
||||
fancy-regex = "0.17.0"
|
||||
fancy-regex = "0.18.0"
|
||||
hex = "0.4"
|
||||
libc = "0.2.153"
|
||||
memchr = "2.7.4"
|
||||
@@ -45,23 +45,21 @@ memmap2 = "0.9"
|
||||
phf = "0.13.0"
|
||||
phf_codegen = "0.13.0"
|
||||
predicates = "3.1.3"
|
||||
rand = { version = "0.9", features = ["small_rng"] }
|
||||
rand = { version = "0.10.0" }
|
||||
regex = "1.10.4"
|
||||
sha2 = "0.10"
|
||||
sha2 = "0.11"
|
||||
sysinfo = "0.38"
|
||||
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.9.0", features = ["libc"] }
|
||||
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
|
||||
@@ -75,7 +73,9 @@ textwrap = { workspace = true }
|
||||
uucore = { 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 +85,13 @@ regex = { workspace = true }
|
||||
sha2 = { workspace = true }
|
||||
tempfile = { workspace = true }
|
||||
uucore = { workspace = true, features = ["entries", "process", "signals", "benchmark"] }
|
||||
uutests = "0.5.0"
|
||||
uutests = "0.9.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 +107,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).
|
||||
@@ -28,7 +28,10 @@ Further work aims to:
|
||||
|
||||
## 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:
|
||||
|
||||
@@ -45,7 +48,8 @@ The binary is named `sed` in `target/release/sed`.
|
||||
|
||||
### 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 +58,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
|
||||
|
||||
@@ -78,6 +87,7 @@ cargo test
|
||||
* 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.
|
||||
|
||||
+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"
|
||||
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.9.0", features = ["libc"] }
|
||||
uufuzz = "0.9.0"
|
||||
|
||||
sed = { path = ".." }
|
||||
|
||||
|
||||
+2
-2
@@ -162,12 +162,12 @@ impl ReplacementTemplate {
|
||||
ReplacementPart::Literal(s) => result.push_str(s),
|
||||
|
||||
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) => {
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+105
-56
@@ -120,7 +120,7 @@ fn patch_block_endings(head: Option<Rc<RefCell<Command>>>) {
|
||||
}
|
||||
|
||||
// 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
|
||||
@@ -140,7 +140,7 @@ fn populate_label_map(
|
||||
mut cur: Option<Rc<RefCell<Command>>>,
|
||||
context: &mut ProcessingContext,
|
||||
) -> UResult<()> {
|
||||
while let Some(rc_cmd) = cur {
|
||||
while let Some(rc_cmd) = cur.take() {
|
||||
// Borrow mutably just long enough to inspect/rewire this node
|
||||
let cmd = rc_cmd.borrow_mut();
|
||||
|
||||
@@ -163,14 +163,14 @@ fn populate_label_map(
|
||||
context.label_to_command_map.insert(label, rc_cmd.clone());
|
||||
}
|
||||
|
||||
cur = cmd.next.clone();
|
||||
cur.clone_from(&cmd.next);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 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) {
|
||||
while let Some(rc_cmd) = cur {
|
||||
while let Some(rc_cmd) = cur.take() {
|
||||
// Borrow mutably just long enough to inspect/rewire this node
|
||||
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));
|
||||
}
|
||||
|
||||
cur = cmd.next.clone();
|
||||
cur.clone_from(&cmd.next);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,7 +194,7 @@ fn resolve_branch_targets(
|
||||
mut cur: Option<Rc<RefCell<Command>>>,
|
||||
context: &mut ProcessingContext,
|
||||
) -> UResult<()> {
|
||||
while let Some(rc_cmd) = cur {
|
||||
while let Some(rc_cmd) = cur.take() {
|
||||
// Borrow mutably just long enough to inspect/rewire this node
|
||||
let mut cmd = rc_cmd.borrow_mut();
|
||||
|
||||
@@ -233,7 +233,7 @@ fn resolve_branch_targets(
|
||||
}
|
||||
|
||||
// Advance to the next sibling
|
||||
cur = cmd.next.clone();
|
||||
cur.clone_from(&cmd.next);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -285,14 +285,14 @@ fn compile_sequence(
|
||||
let mut cmd = Rc::new(RefCell::new(Command::at_position(lines, line)));
|
||||
let n_addr = compile_address_range(lines, line, &mut cmd, context)?;
|
||||
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.
|
||||
let mut cmd_mut = cmd.borrow_mut();
|
||||
cmd_mut.code = line.current();
|
||||
match (cmd_spec.handler)(lines, line, &mut cmd_mut, context)? {
|
||||
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_spec.handler)(lines, line, &mut cmd_mut, context)?;
|
||||
}
|
||||
@@ -498,9 +498,8 @@ fn parse_number(
|
||||
if num_str.is_empty() {
|
||||
if required {
|
||||
return compilation_error(lines, line, "number expected");
|
||||
} else {
|
||||
return Ok(None);
|
||||
}
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
num_str
|
||||
@@ -533,7 +532,7 @@ fn parse_command_ending(
|
||||
}
|
||||
|
||||
/// 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..
|
||||
/// - Escapes ERE-only metacharacters: `+ ? { } | ( )`.
|
||||
/// - Leaves all other characters as-is.
|
||||
@@ -554,6 +553,18 @@ fn bre_to_ere(pattern: &str) -> String {
|
||||
chars.next();
|
||||
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() => {
|
||||
// Back-reference. In sed BREs these are single-digit
|
||||
// (\1-\9) whereas fancy_regex supports multi-digit
|
||||
@@ -668,13 +679,12 @@ pub fn compile_replacement(
|
||||
literal.push('\n');
|
||||
*line = ScriptCharProvider::new(&next_line_string);
|
||||
continue;
|
||||
} else {
|
||||
return compilation_error(
|
||||
lines,
|
||||
line,
|
||||
"unterminated substitute replacement (unexpected EOF)",
|
||||
);
|
||||
}
|
||||
return compilation_error(
|
||||
lines,
|
||||
line,
|
||||
"unterminated substitute replacement (unexpected EOF)",
|
||||
);
|
||||
}
|
||||
|
||||
match line.current() {
|
||||
@@ -706,14 +716,15 @@ pub fn compile_replacement(
|
||||
}
|
||||
|
||||
// other escape sequences
|
||||
_ => match parse_char_escape(line) {
|
||||
Some(decoded) => literal.push(decoded),
|
||||
None => {
|
||||
_ => {
|
||||
if let Some(decoded) = parse_char_escape(line) {
|
||||
literal.push(decoded);
|
||||
} else {
|
||||
literal.push('\\');
|
||||
literal.push(line.current());
|
||||
line.advance();
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1118,7 +1129,6 @@ fn compile_text_command(
|
||||
context: &mut ProcessingContext,
|
||||
) -> UResult<CommandHandling> {
|
||||
line.advance(); // Skip the command character.
|
||||
|
||||
line.eat_spaces(); // Skip any leading whitespace.
|
||||
if context.posix {
|
||||
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
|
||||
let mut escaped_newline = false;
|
||||
|
||||
if line.eol() {
|
||||
return compilation_error(
|
||||
lines,
|
||||
line,
|
||||
format!("command `{}' expects \\ followed by text", cmd.code),
|
||||
);
|
||||
}
|
||||
|
||||
// Skip optional \.
|
||||
if !line.eol() && line.current() == '\\' {
|
||||
line.advance();
|
||||
@@ -1177,13 +1195,12 @@ fn compile_text_command_gnu(
|
||||
continue 'text_content;
|
||||
}
|
||||
|
||||
match parse_char_escape(line) {
|
||||
Some(decoded) => text.push(decoded),
|
||||
None => {
|
||||
// Invalid escapes result in the escaped character.
|
||||
text.push(line.current());
|
||||
line.advance();
|
||||
}
|
||||
if let Some(decoded) = parse_char_escape(line) {
|
||||
text.push(decoded);
|
||||
} else {
|
||||
// Invalid escapes result in the escaped character.
|
||||
text.push(line.current());
|
||||
line.advance();
|
||||
}
|
||||
} else {
|
||||
text.push(line.current());
|
||||
@@ -1236,6 +1253,11 @@ fn compile_text_command_posix(
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if text.is_empty() {
|
||||
compilation_error(lines, line, "incomplete command")?;
|
||||
}
|
||||
|
||||
cmd.data = CommandData::Text(Rc::from(text));
|
||||
Ok(CommandHandling::Continue)
|
||||
}
|
||||
@@ -1246,13 +1268,14 @@ fn get_verified_cmd_spec(
|
||||
lines: &ScriptLineProvider,
|
||||
line: &ScriptCharProvider,
|
||||
n_addr: usize,
|
||||
posix: bool,
|
||||
) -> UResult<CommandSpec> {
|
||||
if line.eol() {
|
||||
return compilation_error(lines, line, "command expected");
|
||||
}
|
||||
|
||||
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 {
|
||||
return compilation_error(
|
||||
@@ -1273,6 +1296,7 @@ fn get_cmd_spec(
|
||||
lines: &ScriptLineProvider,
|
||||
line: &ScriptCharProvider,
|
||||
cmd_code: char,
|
||||
posix: bool,
|
||||
) -> UResult<CommandSpec> {
|
||||
match cmd_code {
|
||||
'!' => Ok(CommandSpec {
|
||||
@@ -1280,7 +1304,7 @@ fn get_cmd_spec(
|
||||
handler: compile_negation_command,
|
||||
}),
|
||||
'=' => Ok(CommandSpec {
|
||||
n_addr: 1,
|
||||
n_addr: if posix { 1 } else { 2 },
|
||||
handler: compile_empty_command,
|
||||
}),
|
||||
':' => Ok(CommandSpec {
|
||||
@@ -1296,7 +1320,7 @@ fn get_cmd_spec(
|
||||
handler: compile_end_group_command,
|
||||
}),
|
||||
'a' | 'i' => Ok(CommandSpec {
|
||||
n_addr: 1,
|
||||
n_addr: if posix { 1 } else { 2 },
|
||||
handler: compile_text_command,
|
||||
}),
|
||||
'b' | 't' => Ok(CommandSpec {
|
||||
@@ -1315,13 +1339,17 @@ fn get_cmd_spec(
|
||||
n_addr: 2,
|
||||
handler: compile_number_command,
|
||||
}),
|
||||
'q' => Ok(CommandSpec {
|
||||
n_addr: if posix { 1 } else { 2 },
|
||||
handler: compile_number_command,
|
||||
}),
|
||||
// Q is a GNU extension
|
||||
'q' | 'Q' => Ok(CommandSpec {
|
||||
'Q' => Ok(CommandSpec {
|
||||
n_addr: 1,
|
||||
handler: compile_number_command,
|
||||
}),
|
||||
'r' => Ok(CommandSpec {
|
||||
n_addr: 1,
|
||||
n_addr: if posix { 1 } else { 2 },
|
||||
handler: compile_read_file_command,
|
||||
}),
|
||||
's' => Ok(CommandSpec {
|
||||
@@ -1373,35 +1401,35 @@ mod tests {
|
||||
#[test]
|
||||
fn test_lookup_empty_command() {
|
||||
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);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lookup_text_command() {
|
||||
let (lines, line) = make_providers("123abc");
|
||||
let cmd = get_cmd_spec(&lines, &line, 'a').unwrap();
|
||||
assert_eq!(cmd.n_addr, 1);
|
||||
let cmd = get_cmd_spec(&lines, &line, 'a', false).unwrap();
|
||||
assert_eq!(cmd.n_addr, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lookup_nonselect_command() {
|
||||
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);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lookup_endgroup_command() {
|
||||
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);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lookup_invalid_command() {
|
||||
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());
|
||||
}
|
||||
|
||||
@@ -1453,7 +1481,7 @@ mod tests {
|
||||
fn test_missing_command_character() {
|
||||
let lines = ScriptLineProvider::with_active_state("test.sed", 1);
|
||||
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());
|
||||
let msg = result.unwrap_err().to_string();
|
||||
@@ -1464,7 +1492,7 @@ mod tests {
|
||||
fn test_invalid_command_character() {
|
||||
let lines = ScriptLineProvider::with_active_state("script.sed", 2);
|
||||
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());
|
||||
let msg = result.unwrap_err().to_string();
|
||||
@@ -1475,7 +1503,7 @@ mod tests {
|
||||
fn test_too_many_addresses() {
|
||||
let lines = ScriptLineProvider::with_active_state("input.sed", 3);
|
||||
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());
|
||||
let msg = result.unwrap_err().to_string();
|
||||
@@ -1488,11 +1516,22 @@ mod tests {
|
||||
fn test_valid_command_spec() {
|
||||
let lines = ScriptLineProvider::with_active_state("input.sed", 4);
|
||||
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());
|
||||
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
|
||||
@@ -2171,7 +2210,7 @@ mod tests {
|
||||
// bre_to_ere
|
||||
#[test]
|
||||
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");
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
fn test_compile_text_command_gnu_optional_backslash() {
|
||||
let mut chars = make_char_provider("athere");
|
||||
@@ -2753,19 +2805,16 @@ mod tests {
|
||||
}
|
||||
|
||||
#[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 lines = make_line_provider(&[]);
|
||||
let mut cmd = Command::default();
|
||||
let mut context = ProcessingContext::default();
|
||||
|
||||
compile_text_command(&mut lines, &mut chars, &mut cmd, &mut context).unwrap();
|
||||
match &cmd.data {
|
||||
CommandData::Text(text) => {
|
||||
assert_eq!(text.to_string(), "\n");
|
||||
}
|
||||
_ => panic!("Expected CommandData::Text"),
|
||||
}
|
||||
let result = compile_text_command(&mut lines, &mut chars, &mut cmd, &mut context);
|
||||
assert!(result.is_err());
|
||||
let err = result.unwrap_err().to_string();
|
||||
assert!(err.contains("expects \\ followed by text"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user