mirror of
https://github.com/uutils/sed.git
synced 2026-06-10 16:14:15 -07:00
Compare commits
70
Commits
0.1.1
...
latest-commit
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d23df4c400 | ||
|
|
8039955354 | ||
|
|
8a2934e519 | ||
|
|
a0d82d678d | ||
|
|
31de5d5ea0 | ||
|
|
78d3b3acf7 | ||
|
|
d9a1cdb9a2 | ||
|
|
1ccd8d069a | ||
|
|
4fb1046c54 | ||
|
|
b0e7940484 | ||
|
|
89b7a039c9 | ||
|
|
398ee76b79 | ||
|
|
ea5a3e45df | ||
|
|
c5e6348825 | ||
|
|
fc39ff7329 | ||
|
|
f6d26ff00f | ||
|
|
c6479b44ad | ||
|
|
8e3d3985e1 | ||
|
|
1710d9cc56 | ||
|
|
b26f59ab2f | ||
|
|
5d452d4174 | ||
|
|
ff0c1cdd06 | ||
|
|
4e024a9576 | ||
|
|
d89243bdb9 | ||
|
|
270f3fdce8 | ||
|
|
69ddd044c5 | ||
|
|
5ff4a7c0c2 | ||
|
|
ce3e4b33b5 | ||
|
|
35ea457759 | ||
|
|
35b6e84c16 | ||
|
|
faea122f87 | ||
|
|
db58849440 | ||
|
|
94bd9b4f14 | ||
|
|
d0ad8eb95d | ||
|
|
df06ef663d | ||
|
|
e82023054e | ||
|
|
9b7b509268 | ||
|
|
70ce8fe410 | ||
|
|
e16a49bb93 | ||
|
|
a8887f83dd | ||
|
|
9667ebe8c3 | ||
|
|
5ed30bd344 | ||
|
|
1996ac2ae3 | ||
|
|
e40ddb1c99 | ||
|
|
8c840078c5 | ||
|
|
4029ae5aba | ||
|
|
3479e9b580 | ||
|
|
0109b369a6 | ||
|
|
77e1abd18e | ||
|
|
17575017d8 | ||
|
|
902611caf1 | ||
|
|
b5a8444f88 | ||
|
|
200bc978b4 | ||
|
|
d58547195b | ||
|
|
ef7f8ad361 | ||
|
|
19f02d5683 | ||
|
|
d253a5808a | ||
|
|
bf47d12e37 | ||
|
|
0ea19a6fd5 | ||
|
|
55177aeca4 | ||
|
|
c8b9173752 | ||
|
|
3fa2227ebf | ||
|
|
2df1e00d8e | ||
|
|
61c405c549 | ||
|
|
3336f93514 | ||
|
|
569f3557e5 | ||
|
|
9ae154a26d | ||
|
|
2ae5b782c7 | ||
|
|
eddee66b6c | ||
|
|
b3ee5bfa9d |
@@ -0,0 +1,80 @@
|
||||
name: GnuComment
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ["GnuTests"]
|
||||
types:
|
||||
- completed
|
||||
|
||||
permissions: {}
|
||||
jobs:
|
||||
post-comment:
|
||||
permissions:
|
||||
actions: read # to list workflow runs artifacts
|
||||
pull-requests: write # to comment on pr
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
if: >
|
||||
github.event.workflow_run.event == 'pull_request'
|
||||
steps:
|
||||
- name: 'Download artifact'
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
// List all artifacts from GnuTests
|
||||
var artifacts = await github.rest.actions.listWorkflowRunArtifacts({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
run_id: ${{ github.event.workflow_run.id }},
|
||||
});
|
||||
|
||||
// Download the "comment" artifact, which contains a PR number (NR) and result.txt
|
||||
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,
|
||||
artifact_id: matchArtifact.id,
|
||||
archive_format: 'zip',
|
||||
});
|
||||
var fs = require('fs');
|
||||
fs.writeFileSync('${{ github.workspace }}/comment.zip', Buffer.from(download.data));
|
||||
- run: unzip comment.zip || echo "Failed to unzip comment artifact"
|
||||
|
||||
- name: 'Comment on PR'
|
||||
uses: actions/github-script@v8
|
||||
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');
|
||||
return;
|
||||
}
|
||||
if (!fs.existsSync('./result.txt')) {
|
||||
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,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue_number,
|
||||
body: 'GNU sed testsuite comparison:\n```\n' + content + '```'
|
||||
});
|
||||
} else {
|
||||
console.log('Comment content too short, skipping');
|
||||
}
|
||||
@@ -0,0 +1,284 @@
|
||||
name: GnuTests
|
||||
|
||||
# Run GNU sed testsuite against the Rust sed implementation
|
||||
# This workflow extracts and runs tests from the GNU sed testsuite to ensure compatibility
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
|
||||
permissions:
|
||||
contents: write # Publish sed instead of discarding
|
||||
|
||||
# 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' }}
|
||||
|
||||
env:
|
||||
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
|
||||
TEST_FULL_SUMMARY_FILE: 'sed-gnu-full-result.json'
|
||||
|
||||
jobs:
|
||||
native:
|
||||
name: Run GNU sed testsuite
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
#### Get the code, setup cache
|
||||
- name: Checkout code (sed)
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
path: 'sed'
|
||||
persist-credentials: false
|
||||
- uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: stable
|
||||
components: rustfmt
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: "./sed -> target"
|
||||
- name: Checkout code (GNU sed testsuite)
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
repository: 'mirror/sed'
|
||||
path: 'gnu.sed'
|
||||
ref: 'master'
|
||||
persist-credentials: false
|
||||
|
||||
# Omit installing part of https://github.com/actions/runner-images/tree/main/images/ubuntu
|
||||
|
||||
### Build
|
||||
- name: Build Rust sed binary
|
||||
shell: bash
|
||||
run: |
|
||||
## 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
|
||||
- name: Publish latest commit
|
||||
uses: softprops/action-gh-release@v2
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
with:
|
||||
tag_name: latest-commit
|
||||
draft: false
|
||||
prerelease: true
|
||||
files: |
|
||||
sed-x86_64-unknown-linux-gnu.zst
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
### Run tests
|
||||
- name: Run GNU sed testsuite
|
||||
shell: bash
|
||||
run: |
|
||||
## Run GNU sed testsuite using our script
|
||||
cd 'sed'
|
||||
# Set GNU testsuite directory
|
||||
export GNU_TESTSUITE_DIR="../gnu.sed/testsuite"
|
||||
# Run tests with JSON output
|
||||
./util/run-gnu-testsuite.sh --json-output "${{ env.TEST_FULL_SUMMARY_FILE }}" || true
|
||||
|
||||
### Upload artifacts
|
||||
- name: Check for JSON results file
|
||||
shell: bash
|
||||
run: |
|
||||
echo "Checking for JSON results file..."
|
||||
ls -la sed/ || true
|
||||
ls -la sed/${{ env.TEST_FULL_SUMMARY_FILE }} || echo "JSON file not found at sed/${{ env.TEST_FULL_SUMMARY_FILE }}"
|
||||
ls -la ${{ env.TEST_FULL_SUMMARY_FILE }} || echo "JSON file not found at ${{ env.TEST_FULL_SUMMARY_FILE }}"
|
||||
find . -name "*json*" -type f || echo "No JSON files found"
|
||||
|
||||
- name: Upload full json results
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: sed-gnu-full-result
|
||||
path: sed/${{ env.TEST_FULL_SUMMARY_FILE }}
|
||||
if-no-files-found: warn
|
||||
|
||||
- name: Upload test logs
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: test-logs
|
||||
path: |
|
||||
sed/test-logs/*.log
|
||||
sed/test-results/*.json
|
||||
|
||||
aggregate:
|
||||
needs: [native]
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
pull-requests: read
|
||||
name: Aggregate GNU test results
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Initialize workflow variables
|
||||
id: vars
|
||||
shell: bash
|
||||
run: |
|
||||
## VARs setup
|
||||
outputs() { step_id="${{ github.action }}"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo "${var}=${!var}" >> $GITHUB_OUTPUT; done; }
|
||||
|
||||
TEST_SUMMARY_FILE='sed-gnu-result.json'
|
||||
outputs TEST_SUMMARY_FILE
|
||||
|
||||
- name: Checkout code (sed)
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
path: 'sed'
|
||||
persist-credentials: false
|
||||
|
||||
- name: Retrieve reference artifacts
|
||||
uses: dawidd6/action-download-artifact@v12
|
||||
continue-on-error: true
|
||||
with:
|
||||
workflow: GnuTests.yml
|
||||
branch: "${{ env.DEFAULT_BRANCH }}"
|
||||
workflow_conclusion: completed
|
||||
path: "reference"
|
||||
if_no_artifact_found: warn
|
||||
|
||||
- name: Download full json results
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: sed-gnu-full-result
|
||||
path: results
|
||||
|
||||
- name: Extract/summarize testing info
|
||||
id: summary
|
||||
shell: bash
|
||||
run: |
|
||||
## Extract/summarize testing info
|
||||
outputs() { step_id="${{ github.action }}"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo "${var}=${!var}" >> $GITHUB_OUTPUT; done; }
|
||||
|
||||
# Check if results directory exists and has JSON files
|
||||
json_count=0
|
||||
if [[ -d "results" ]]; then
|
||||
json_count=$(find results -name "*.json" | wc -l)
|
||||
fi
|
||||
|
||||
if [[ "$json_count" -lt 1 ]]; then
|
||||
echo "::error ::Failed to download results json files; failing early"
|
||||
echo "::error ::Contents of results directory:"
|
||||
ls -lR results || echo "::error ::Results directory does not exist"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Extract summary from JSON results
|
||||
RESULT_FILE="results/${{ env.TEST_FULL_SUMMARY_FILE }}"
|
||||
if [[ -f "$RESULT_FILE" ]]; then
|
||||
TOTAL=$(jq -r '.summary.total // 0' "$RESULT_FILE")
|
||||
PASS=$(jq -r '.summary.passed // 0' "$RESULT_FILE")
|
||||
FAIL=$(jq -r '.summary.failed // 0' "$RESULT_FILE")
|
||||
SKIP=$(jq -r '.summary.skipped // 0' "$RESULT_FILE")
|
||||
ERROR=0 # Our format doesn't distinguish errors from failures
|
||||
else
|
||||
echo "::error ::Result file $RESULT_FILE not found"
|
||||
echo "::error ::Available files in results:"
|
||||
find results -type f || true
|
||||
TOTAL=0; PASS=0; FAIL=0; SKIP=0; ERROR=0
|
||||
fi
|
||||
|
||||
output="GNU sed tests summary = TOTAL: $TOTAL / PASS: $PASS / FAIL: $FAIL / SKIP: $SKIP"
|
||||
echo "${output}"
|
||||
|
||||
if [[ "$FAIL" -gt 0 ]]; then
|
||||
echo "::warning ::${output}"
|
||||
fi
|
||||
|
||||
jq -n \
|
||||
--arg date "$(date --rfc-email)" \
|
||||
--arg sha "$GITHUB_SHA" \
|
||||
--arg total "$TOTAL" \
|
||||
--arg pass "$PASS" \
|
||||
--arg skip "$SKIP" \
|
||||
--arg fail "$FAIL" \
|
||||
--arg error "$ERROR" \
|
||||
'{($date): { sha: $sha, total: $total, pass: $pass, skip: $skip, fail: $fail, error: $error }}' > '${{ steps.vars.outputs.TEST_SUMMARY_FILE }}'
|
||||
|
||||
HASH=$(sha1sum '${{ steps.vars.outputs.TEST_SUMMARY_FILE }}' | cut --delim=" " -f 1)
|
||||
outputs HASH TOTAL PASS FAIL SKIP
|
||||
|
||||
- name: Upload SHA1/ID of 'test-summary'
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: "${{ steps.summary.outputs.HASH }}"
|
||||
path: "${{ steps.vars.outputs.TEST_SUMMARY_FILE }}"
|
||||
|
||||
- name: Upload test results summary
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: test-summary
|
||||
path: "${{ steps.vars.outputs.TEST_SUMMARY_FILE }}"
|
||||
|
||||
- name: Compare test failures VS reference
|
||||
shell: bash
|
||||
run: |
|
||||
## Compare test failures VS reference using JSON files
|
||||
REF_SUMMARY_FILE='reference/sed-gnu-full-result/sed-gnu-full-result.json'
|
||||
CURRENT_SUMMARY_FILE="results/${{ env.TEST_FULL_SUMMARY_FILE }}"
|
||||
REPO_DEFAULT_BRANCH='${{ env.DEFAULT_BRANCH }}'
|
||||
|
||||
# Path to ignore file for intermittent issues
|
||||
IGNORE_INTERMITTENT="sed/.github/workflows/ignore-intermittent.txt"
|
||||
|
||||
# Set up comment directory
|
||||
COMMENT_DIR="reference/comment"
|
||||
mkdir -p ${COMMENT_DIR}
|
||||
echo ${{ github.event.number }} > ${COMMENT_DIR}/NR
|
||||
COMMENT_LOG="${COMMENT_DIR}/result.txt"
|
||||
|
||||
COMPARISON_RESULT=0
|
||||
if test -f "${CURRENT_SUMMARY_FILE}"; then
|
||||
if test -f "${REF_SUMMARY_FILE}"; then
|
||||
echo "Reference summary SHA1/ID: $(sha1sum -- "${REF_SUMMARY_FILE}")"
|
||||
echo "Current summary SHA1/ID: $(sha1sum -- "${CURRENT_SUMMARY_FILE}")"
|
||||
|
||||
python3 sed/util/compare_test_results.py \
|
||||
--ignore-file "${IGNORE_INTERMITTENT}" \
|
||||
--output "${COMMENT_LOG}" \
|
||||
"${CURRENT_SUMMARY_FILE}" "${REF_SUMMARY_FILE}"
|
||||
|
||||
COMPARISON_RESULT=$?
|
||||
else
|
||||
echo "::warning ::Skipping test comparison; no prior reference summary is available at '${REF_SUMMARY_FILE}'."
|
||||
fi
|
||||
else
|
||||
echo "::error ::Failed to find summary of test results (missing '${CURRENT_SUMMARY_FILE}'); failing early"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ${COMPARISON_RESULT} -eq 1 ]; then
|
||||
echo "ONLY_INTERMITTENT=false" >> $GITHUB_ENV
|
||||
echo "::error ::Found new non-intermittent test failures"
|
||||
exit 1
|
||||
else
|
||||
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
|
||||
with:
|
||||
name: comment
|
||||
path: reference/comment/
|
||||
|
||||
- name: Report test results
|
||||
if: success() || failure()
|
||||
shell: bash
|
||||
run: |
|
||||
## Report final results
|
||||
echo "::notice ::GNU sed testsuite results:"
|
||||
echo "::notice :: Total tests: ${{ steps.summary.outputs.TOTAL }}"
|
||||
echo "::notice :: Passed: ${{ steps.summary.outputs.PASS }}"
|
||||
echo "::notice :: Failed: ${{ steps.summary.outputs.FAIL }}"
|
||||
echo "::notice :: Skipped: ${{ steps.summary.outputs.SKIP }}"
|
||||
|
||||
if [[ "${{ steps.summary.outputs.FAIL }}" -gt 0 ]]; then
|
||||
PASS_RATE=$(( ${{ steps.summary.outputs.PASS }} * 100 / (${{ steps.summary.outputs.PASS }} + ${{ steps.summary.outputs.FAIL }}) ))
|
||||
echo "::notice :: Pass rate: ${PASS_RATE}%"
|
||||
fi
|
||||
@@ -69,9 +69,8 @@ jobs:
|
||||
run: |
|
||||
## `cargo clippy` lint testing
|
||||
unset fault
|
||||
CLIPPY_FLAGS="-W clippy::default_trait_access -W clippy::manual_string_new -W clippy::cognitive_complexity -W clippy::implicit_clone -W clippy::range-plus-one -W clippy::redundant-clone -W clippy::match_bool -W clippy::semicolon_if_nothing_returned"
|
||||
fault_type="${{ steps.vars.outputs.FAULT_TYPE }}"
|
||||
fault_prefix=$(echo "$fault_type" | tr '[:lower:]' '[:upper:]')
|
||||
# * convert any warnings to GHA UI annotations; ref: <https://help.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-a-warning-message>
|
||||
S=$(cargo clippy --all-targets --workspace -psed -- ${CLIPPY_FLAGS} -D warnings 2>&1) && printf "%s\n" "$S" || { printf "%s\n" "$S" ; printf "%s" "$S" | sed -E -n -e '/^error:/{' -e "N; s/^error:[[:space:]]+(.*)\\n[[:space:]]+-->[[:space:]]+(.*):([0-9]+):([0-9]+).*$/::${fault_type} file=\2,line=\3,col=\4::${fault_prefix}: \`cargo clippy\`: \1 (file:'\2', line:\3)/p;" -e '}' ; fault=true ; }
|
||||
S=$(cargo clippy --all-targets --workspace -psed -- -D warnings 2>&1) && printf "%s\n" "$S" || { printf "%s\n" "$S" ; printf "%s" "$S" | sed -E -n -e '/^error:/{' -e "N; s/^error:[[:space:]]+(.*)\\n[[:space:]]+-->[[:space:]]+(.*):([0-9]+):([0-9]+).*$/::${fault_type} file=\2,line=\3,col=\4::${fault_prefix}: \`cargo clippy\`: \1 (file:'\2', line:\3)/p;" -e '}' ; fault=true ; }
|
||||
if [ -n "${{ steps.vars.outputs.FAIL_ON_FAULT }}" ] && [ -n "$fault" ]; then exit 1 ; fi
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
# List of intermittent test names to ignore in result comparisons
|
||||
# Format: one test name per line, lines starting with # are comments
|
||||
#
|
||||
# Add test names that are known to be flaky or environment-dependent
|
||||
# Example:
|
||||
# basic_substitution
|
||||
# line_address_test
|
||||
Generated
+128
-60
@@ -56,7 +56,7 @@ version = "1.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
|
||||
dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -67,7 +67,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"once_cell_polyfill",
|
||||
"windows-sys 0.61.2",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -127,6 +127,15 @@ version = "2.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.10.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
version = "1.12.1"
|
||||
@@ -167,9 +176,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.42"
|
||||
version = "0.4.43"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2"
|
||||
checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118"
|
||||
dependencies = [
|
||||
"iana-time-zone",
|
||||
"num-traits",
|
||||
@@ -178,18 +187,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.53"
|
||||
version = "4.5.55"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8"
|
||||
checksum = "3e34525d5bbbd55da2bb745d34b36121baac88d07619a9a09cfcf4a6c0832785"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.53"
|
||||
version = "4.5.55"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00"
|
||||
checksum = "59a20016a20a3da95bef50ec7238dbd09baeef4311dcdd38ec15aba69812fb61"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
@@ -200,9 +209,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_complete"
|
||||
version = "4.5.61"
|
||||
version = "4.5.65"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39615915e2ece2550c0149addac32fb5bd312c657f43845bb9088cb9c8a7c992"
|
||||
checksum = "430b4dc2b5e3861848de79627b2bedc9f3342c7da5173a14eaa5d0f8dc18ae5d"
|
||||
dependencies = [
|
||||
"clap",
|
||||
]
|
||||
@@ -225,9 +234,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "codspeed"
|
||||
version = "4.1.0"
|
||||
version = "4.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3b847e05a34be5c38f3f2a5052178a3bd32e6b5702f3ea775efde95c483a539"
|
||||
checksum = "38c2eb3388ebe26b5a0ab6bf4969d9c4840143d7f6df07caa3cc851b0606cef6"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"cc",
|
||||
@@ -243,9 +252,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "codspeed-divan-compat"
|
||||
version = "4.1.0"
|
||||
version = "4.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0f0e9fe5eaa39995ec35e46407f7154346cc25bd1300c64c21636f3d00cb2cc"
|
||||
checksum = "b2de65b7489a59709724d489070c6d05b7744039e4bf751d0a2006b90bb5593d"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"codspeed",
|
||||
@@ -256,9 +265,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "codspeed-divan-compat-macros"
|
||||
version = "4.1.0"
|
||||
version = "4.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88c8babf2a40fd2206a2e030cf020d0d58144cd56e1dc408bfba02cdefb08b4f"
|
||||
checksum = "56ca01ce4fd22b8dcc6c770dcd6b74343642e842482b94e8920d14e10c57638d"
|
||||
dependencies = [
|
||||
"divan-macros",
|
||||
"itertools",
|
||||
@@ -270,9 +279,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "codspeed-divan-compat-walltime"
|
||||
version = "4.1.0"
|
||||
version = "4.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f26092328e12a36704ffc552f379c6405dd94d3149970b79b22d371717c2aae"
|
||||
checksum = "720ab9d0714718afe5f5832be6e5f5eb5ce97836e24ca7bf7042eea4308b9fb8"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"clap",
|
||||
@@ -311,6 +320,15 @@ version = "0.8.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-deque"
|
||||
version = "0.8.6"
|
||||
@@ -336,6 +354,16 @@ version = "0.8.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ctor"
|
||||
version = "0.6.3"
|
||||
@@ -373,6 +401,16 @@ version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8"
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"crypto-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "displaydoc"
|
||||
version = "0.2.5"
|
||||
@@ -447,7 +485,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.61.2",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -527,6 +565,16 @@ dependencies = [
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.16"
|
||||
@@ -586,6 +634,12 @@ version = "0.16.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
|
||||
|
||||
[[package]]
|
||||
name = "hex"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.64"
|
||||
@@ -598,7 +652,7 @@ dependencies = [
|
||||
"js-sys",
|
||||
"log",
|
||||
"wasm-bindgen",
|
||||
"windows-core 0.62.2",
|
||||
"windows-core 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -688,7 +742,7 @@ dependencies = [
|
||||
"portable-atomic",
|
||||
"portable-atomic-util",
|
||||
"serde_core",
|
||||
"windows-sys 0.61.2",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -735,9 +789,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.178"
|
||||
version = "0.2.180"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091"
|
||||
checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
@@ -1086,15 +1140,15 @@ checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "1.1.2"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e"
|
||||
checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.61.2",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1130,10 +1184,10 @@ dependencies = [
|
||||
"codspeed-divan-compat",
|
||||
"ctor",
|
||||
"fancy-regex",
|
||||
"hex",
|
||||
"libc",
|
||||
"memchr",
|
||||
"memmap2",
|
||||
"once_cell",
|
||||
"phf",
|
||||
"phf_codegen",
|
||||
"predicates",
|
||||
@@ -1141,6 +1195,7 @@ dependencies = [
|
||||
"rand",
|
||||
"regex",
|
||||
"rlimit",
|
||||
"sha2",
|
||||
"sysinfo",
|
||||
"tempfile",
|
||||
"terminal_size",
|
||||
@@ -1199,6 +1254,17 @@ dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.10.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
@@ -1262,9 +1328,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sysinfo"
|
||||
version = "0.37.2"
|
||||
version = "0.38.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "16607d5caffd1c07ce073528f9ed972d88db15dd44023fa57142963be3feb11f"
|
||||
checksum = "fe840c5b1afe259a5657392a4dbb74473a14c8db999c3ec2f4ae812e028a94da"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"memchr",
|
||||
@@ -1276,15 +1342,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.23.0"
|
||||
version = "3.24.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16"
|
||||
checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c"
|
||||
dependencies = [
|
||||
"fastrand",
|
||||
"getrandom 0.3.4",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"windows-sys 0.61.2",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1418,6 +1484,12 @@ dependencies = [
|
||||
"rustc-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
|
||||
|
||||
[[package]]
|
||||
name = "unic-langid"
|
||||
version = "0.9.6"
|
||||
@@ -1513,6 +1585,12 @@ dependencies = [
|
||||
"xattr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.5.0"
|
||||
@@ -1614,7 +1692,7 @@ version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||
dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1625,24 +1703,23 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows"
|
||||
version = "0.61.3"
|
||||
version = "0.62.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893"
|
||||
checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580"
|
||||
dependencies = [
|
||||
"windows-collections",
|
||||
"windows-core 0.61.2",
|
||||
"windows-core 0.62.2",
|
||||
"windows-future",
|
||||
"windows-link 0.1.3",
|
||||
"windows-numerics",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-collections"
|
||||
version = "0.2.0"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8"
|
||||
checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610"
|
||||
dependencies = [
|
||||
"windows-core 0.61.2",
|
||||
"windows-core 0.62.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1673,12 +1750,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windows-future"
|
||||
version = "0.2.1"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e"
|
||||
checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb"
|
||||
dependencies = [
|
||||
"windows-core 0.61.2",
|
||||
"windows-link 0.1.3",
|
||||
"windows-core 0.62.2",
|
||||
"windows-link 0.2.1",
|
||||
"windows-threading",
|
||||
]
|
||||
|
||||
@@ -1718,12 +1795,12 @@ checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||
|
||||
[[package]]
|
||||
name = "windows-numerics"
|
||||
version = "0.2.0"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1"
|
||||
checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26"
|
||||
dependencies = [
|
||||
"windows-core 0.61.2",
|
||||
"windows-link 0.1.3",
|
||||
"windows-core 0.62.2",
|
||||
"windows-link 0.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1780,15 +1857,6 @@ dependencies = [
|
||||
"windows-targets 0.53.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.61.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
||||
dependencies = [
|
||||
"windows-link 0.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.6"
|
||||
@@ -1824,11 +1892,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windows-threading"
|
||||
version = "0.1.0"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6"
|
||||
checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37"
|
||||
dependencies = [
|
||||
"windows-link 0.1.3",
|
||||
"windows-link 0.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
+15
-8
@@ -38,16 +38,17 @@ clap_complete = "4.5"
|
||||
clap_mangen = "0.2"
|
||||
divan = { package = "codspeed-divan-compat", version = "4.0.5" }
|
||||
fancy-regex = "0.17.0"
|
||||
hex = "0.4"
|
||||
libc = "0.2.153"
|
||||
memchr = "2.7.4"
|
||||
memmap2 = "0.9"
|
||||
once_cell = "1.21.3"
|
||||
phf = "0.13.0"
|
||||
phf_codegen = "0.13.0"
|
||||
predicates = "3.1.3"
|
||||
rand = { version = "0.9", features = ["small_rng"] }
|
||||
regex = "1.10.4"
|
||||
sysinfo = "0.37"
|
||||
sha2 = "0.10"
|
||||
sysinfo = "0.38"
|
||||
tempfile = "3.10.1"
|
||||
textwrap = { version = "0.16.1", features = ["terminal_size"] }
|
||||
terminal_size = "0.4.2"
|
||||
@@ -64,7 +65,6 @@ ctor = "0.6.0"
|
||||
fancy-regex = { workspace = true }
|
||||
memchr = { workspace = true }
|
||||
memmap2.workspace = true
|
||||
once_cell = { workspace = true }
|
||||
phf = { workspace = true }
|
||||
predicates = { workspace = true }
|
||||
regex = { workspace = true }
|
||||
@@ -77,10 +77,12 @@ uucore = { workspace = true }
|
||||
[dev-dependencies]
|
||||
chrono = { workspace = true }
|
||||
divan = { workspace = true }
|
||||
hex = { workspace = true }
|
||||
libc = { workspace = true }
|
||||
pretty_assertions = "1"
|
||||
rand = { workspace = true }
|
||||
regex = { workspace = true }
|
||||
sha2 = { workspace = true }
|
||||
tempfile = { workspace = true }
|
||||
uucore = { workspace = true, features = ["entries", "process", "signals", "benchmark"] }
|
||||
uutests = "0.5.0"
|
||||
@@ -116,7 +118,7 @@ lto = true
|
||||
[profile.release-fast]
|
||||
inherits = "release"
|
||||
panic = "abort"
|
||||
codegen-units = 1
|
||||
codegen-units = 1 # should be moved to release without regression
|
||||
|
||||
# A release-like profile that is as small as possible.
|
||||
[profile.release-small]
|
||||
@@ -124,7 +126,12 @@ inherits = "release-fast"
|
||||
opt-level = "z"
|
||||
strip = true
|
||||
|
||||
# The profile that 'cargo dist' will build with
|
||||
[profile.dist]
|
||||
inherits = "release"
|
||||
lto = "thin"
|
||||
[lints.clippy]
|
||||
default_trait_access = "warn"
|
||||
manual_string_new = "warn"
|
||||
cognitive_complexity = "warn"
|
||||
implicit_clone = "warn"
|
||||
range-plus-one = "warn"
|
||||
redundant-clone = "warn"
|
||||
match_bool = "warn"
|
||||
semicolon_if_nothing_returned = "warn"
|
||||
|
||||
@@ -41,6 +41,31 @@ cargo run --release
|
||||
|
||||
The binary is named `sed` in `target/release/sed`.
|
||||
|
||||
## Testing
|
||||
|
||||
### GNU sed Compatibility Testing
|
||||
|
||||
Test compatibility against GNU sed using the comprehensive testsuite (47+ tests, ~10% pass rate):
|
||||
|
||||
```bash
|
||||
# Clone GNU sed testsuite (one time setup)
|
||||
git clone https://github.com/mirror/sed.git ../gnu.sed
|
||||
|
||||
# Run compatibility tests
|
||||
./util/run-gnu-testsuite.sh
|
||||
|
||||
# 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.
|
||||
|
||||
### Unit Tests
|
||||
|
||||
```bash
|
||||
cargo test
|
||||
```
|
||||
|
||||
## Extensions and incompatibilities
|
||||
### Supported GNU extensions
|
||||
* Command-line arguments can be specified in long (`--`) form.
|
||||
@@ -58,6 +83,10 @@ The binary is named `sed` in `target/release/sed`.
|
||||
* The `q` command can be optionally followed by an exit code.
|
||||
* The `l` command can be optionally followed by the output width.
|
||||
* The `--follow-symlinks` flag for in-place editing.
|
||||
* Address 0 can be used to specify an address range that is already
|
||||
active on line 1 and can finish with the specified regular expression.
|
||||
* Address steps can be specified in the form of start~step and start,~step
|
||||
ranges.
|
||||
|
||||
### Supported BSD and GNU extensions
|
||||
* The second address in a range can be specified as a relative address with +N.
|
||||
@@ -87,6 +116,13 @@ The binary is named `sed` in `target/release/sed`.
|
||||
* Labels are parsed for alphanumeric characters. The BSD version parses them
|
||||
until the end of the line, preventing ; to be used as a separator.
|
||||
|
||||
## GNU test suite compatibility
|
||||
|
||||
Below is the evolution of how many GNU tests uutils passes.
|
||||
|
||||

|
||||
|
||||
|
||||
## License
|
||||
|
||||
sed is licensed under the MIT License - see the `LICENSE` file for details
|
||||
|
||||
@@ -17,7 +17,7 @@ fn no_op_short(bencher: Bencher) {
|
||||
let temp_dir = tempfile::tempdir().unwrap();
|
||||
let mut data = Vec::new();
|
||||
for i in 0..10_000_000 {
|
||||
data.extend_from_slice(format!("{}\n", i).as_bytes());
|
||||
data.extend_from_slice(format!("{i}\n").as_bytes());
|
||||
}
|
||||
let file_path = create_test_file(&data, temp_dir.path());
|
||||
let file_path_str = file_path.to_str().unwrap();
|
||||
@@ -192,7 +192,7 @@ fn remove_cr(bencher: Bencher) {
|
||||
let temp_dir = tempfile::tempdir().unwrap();
|
||||
let mut data = Vec::new();
|
||||
for i in 0..500_000 {
|
||||
let line = format!("line {} with windows endings\r\n", i);
|
||||
let line = format!("line {i} with windows endings\r\n");
|
||||
data.extend_from_slice(line.as_bytes());
|
||||
}
|
||||
let file_path = create_test_file(&data, temp_dir.path());
|
||||
@@ -240,7 +240,7 @@ fn number_fix(bencher: Bencher) {
|
||||
let cents = i % 100;
|
||||
let thousands = euros / 1000;
|
||||
let remainder = euros % 1000;
|
||||
let line = format!("{}.{:03},{:02}\n", thousands, remainder, cents);
|
||||
let line = format!("{thousands}.{remainder:03},{cents:02}\n");
|
||||
data.extend_from_slice(line.as_bytes());
|
||||
}
|
||||
let file_path = create_test_file(&data, temp_dir.path());
|
||||
|
||||
Generated
+11
-12
@@ -579,9 +579,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.178"
|
||||
version = "0.2.180"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091"
|
||||
checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
|
||||
|
||||
[[package]]
|
||||
name = "libfuzzer-sys"
|
||||
@@ -601,9 +601,9 @@ checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.9.4"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
|
||||
checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
@@ -923,15 +923,15 @@ checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "1.0.7"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266"
|
||||
checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.61.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -951,7 +951,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sed"
|
||||
version = "0.0.1"
|
||||
version = "0.1.1"
|
||||
dependencies = [
|
||||
"assert_fs",
|
||||
"clap",
|
||||
@@ -961,7 +961,6 @@ dependencies = [
|
||||
"fancy-regex",
|
||||
"memchr",
|
||||
"memmap2",
|
||||
"once_cell",
|
||||
"phf",
|
||||
"phf_codegen",
|
||||
"predicates",
|
||||
@@ -1068,15 +1067,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.23.0"
|
||||
version = "3.24.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16"
|
||||
checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c"
|
||||
dependencies = [
|
||||
"fastrand",
|
||||
"getrandom",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.61.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
+11
-20
@@ -64,6 +64,8 @@ pub struct ProcessingContext {
|
||||
pub parsed_block_nesting: usize,
|
||||
/// Command associated with each label
|
||||
pub label_to_command_map: HashMap<String, Rc<RefCell<Command>>>,
|
||||
/// Commands with a (latchable and resetable) address range
|
||||
pub range_commands: Vec<Rc<RefCell<Command>>>,
|
||||
/// True if a substitution was made as specified in the t command
|
||||
pub substitution_made: bool,
|
||||
/// Elements to append at the end of each command processing cycle
|
||||
@@ -84,26 +86,15 @@ pub struct StringSpace {
|
||||
pub has_newline: bool, // True if \n-terminated
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[derive(Debug)]
|
||||
/// Types of address specifications that precede commands
|
||||
pub enum AddressType {
|
||||
Re, // Line that matches regex
|
||||
Line, // Specific line
|
||||
RelLine, // Relative line
|
||||
Last, // Last line
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
/// Format of an address
|
||||
pub struct Address {
|
||||
pub atype: AddressType, // Address type
|
||||
pub value: AddressValue, // Line number or regex
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum AddressValue {
|
||||
LineNumber(usize),
|
||||
Regex(Option<Regex>),
|
||||
pub enum Address {
|
||||
Re(Option<Regex>), // Line that matches (optional) regex
|
||||
Line(usize), // Specific line
|
||||
RelLine(usize), // Relative line
|
||||
Last, // Last line
|
||||
StepMatch(usize), // Lines matching specified step from first
|
||||
StepEnd(usize), // Range ending at specified step from first
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -278,7 +269,7 @@ pub struct Command {
|
||||
pub addr1: Option<Address>, // Start address
|
||||
pub addr2: Option<Address>, // End address
|
||||
pub non_select: bool, // True if '!'
|
||||
pub start_line: Option<usize>, // Start line number (or None)
|
||||
pub start_line: Option<usize>, // Start line number (or None if unlatched)
|
||||
pub data: CommandData, // Command-specific data
|
||||
pub next: Option<Rc<RefCell<Command>>>, // Pointer to next command
|
||||
pub location: ScriptLocation, // Command's definition location
|
||||
|
||||
+297
-161
File diff suppressed because it is too large
Load Diff
+87
-88
@@ -61,7 +61,6 @@ pub struct MmapLineCursor<'a> {
|
||||
pub struct NextMmapLine<'a> {
|
||||
pub content: &'a [u8],
|
||||
pub full_span: &'a [u8],
|
||||
pub is_last_line: bool,
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
@@ -76,7 +75,7 @@ impl<'a> MmapLineCursor<'a> {
|
||||
}
|
||||
|
||||
/// Return the next line, if available, or None.
|
||||
fn get_line(&mut self) -> io::Result<Option<NextMmapLine<'_>>> {
|
||||
fn get_line(&mut self) -> io::Result<Option<NextMmapLine<'a>>> {
|
||||
if self.pos >= self.data.len() {
|
||||
return Ok(None);
|
||||
}
|
||||
@@ -101,12 +100,12 @@ impl<'a> MmapLineCursor<'a> {
|
||||
full_span
|
||||
};
|
||||
|
||||
let is_last_line = self.pos >= self.data.len();
|
||||
Ok(Some(NextMmapLine {
|
||||
content,
|
||||
full_span,
|
||||
is_last_line,
|
||||
}))
|
||||
Ok(Some(NextMmapLine { content, full_span }))
|
||||
}
|
||||
|
||||
/// Return true if the previously returned line was the last one.
|
||||
fn last_line(&mut self) -> io::Result<bool> {
|
||||
Ok(self.pos >= self.data.len())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,9 +125,8 @@ impl ReadLineCursor {
|
||||
}
|
||||
}
|
||||
|
||||
/// If a line is available, return it, its \n termination,
|
||||
/// and next line availability, otherwise return None.
|
||||
fn get_line(&mut self) -> io::Result<Option<(String, bool, bool)>> {
|
||||
/// If a line is available, return it and its \n termination.
|
||||
fn get_line(&mut self) -> io::Result<Option<(String, bool)>> {
|
||||
self.buffer.clear();
|
||||
// read_line *includes* the '\n' if present
|
||||
let bytes_read = self.reader.read_line(&mut self.buffer)?;
|
||||
@@ -142,8 +140,14 @@ impl ReadLineCursor {
|
||||
self.buffer.pop();
|
||||
}
|
||||
let line = std::mem::take(&mut self.buffer);
|
||||
let is_last_line = self.reader.fill_buf()?.is_empty();
|
||||
Ok(Some((line, has_newline, is_last_line)))
|
||||
Ok(Some((line, has_newline)))
|
||||
}
|
||||
|
||||
/// Return true if the previously returned line was the last one.
|
||||
fn last_line(&mut self) -> io::Result<bool> {
|
||||
// FIXME(rust-lang#86423): Replace with BufRead::has_data_left()
|
||||
// when/if method becomes stable.
|
||||
Ok(self.reader.fill_buf()?.is_empty())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -466,21 +470,15 @@ impl<'a> LineReader<'a> {
|
||||
line_reader_read_input(file)
|
||||
}
|
||||
|
||||
/// Return the next line, if available and also the availability
|
||||
/// of another one, or None at end of file.
|
||||
pub fn get_line(&mut self) -> io::Result<Option<(IOChunk<'_>, bool)>> {
|
||||
/// Return the next line, if available.
|
||||
pub fn get_line(&mut self) -> io::Result<Option<IOChunk<'a>>> {
|
||||
match self {
|
||||
#[cfg(unix)]
|
||||
LineReader::MmapInput { cursor, .. } => {
|
||||
// Obtain fields to prevent borrowing issues.
|
||||
let fast_copy = cursor.fast_copy.clone();
|
||||
let base = cursor.data.as_ptr();
|
||||
if let Some(NextMmapLine {
|
||||
content,
|
||||
full_span,
|
||||
is_last_line,
|
||||
}) = cursor.get_line()?
|
||||
{
|
||||
if let Some(NextMmapLine { content, full_span }) = cursor.get_line()? {
|
||||
let chunk = IOChunk::from_content(IOChunkContent::MmapInput {
|
||||
fast_copy,
|
||||
base,
|
||||
@@ -488,17 +486,17 @@ impl<'a> LineReader<'a> {
|
||||
full_span,
|
||||
});
|
||||
|
||||
Ok(Some((chunk, is_last_line)))
|
||||
Ok(Some(chunk))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
LineReader::ReadInput(cursor) => {
|
||||
if let Some((line, _has_newline, is_last_line)) = cursor.get_line()? {
|
||||
if let Some((line, _has_newline)) = cursor.get_line()? {
|
||||
let chunk =
|
||||
IOChunk::from_content(IOChunkContent::new_owned(line, _has_newline));
|
||||
Ok(Some((chunk, is_last_line)))
|
||||
Ok(Some(chunk))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
@@ -508,6 +506,19 @@ impl<'a> LineReader<'a> {
|
||||
LineReader::_Phantom(_) => unreachable!("_Phantom should never be constructed"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return true if the previously returned line was the last one.
|
||||
pub fn last_line(&mut self) -> io::Result<bool> {
|
||||
match self {
|
||||
#[cfg(unix)]
|
||||
LineReader::MmapInput { cursor, .. } => cursor.last_line(),
|
||||
|
||||
LineReader::ReadInput(cursor) => cursor.last_line(),
|
||||
|
||||
#[cfg(not(unix))]
|
||||
LineReader::_Phantom(_) => unreachable!("_Phantom should never be constructed"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Define a trait combining two: workaround for Rust's corresponding inability.
|
||||
@@ -1048,7 +1059,7 @@ mod tests {
|
||||
let mut out = OutputBuffer::new(Box::new(Box::new(out_file)));
|
||||
|
||||
// Drain reader → writer
|
||||
while let Some((chunk, _last_line)) = reader.get_line()? {
|
||||
while let Some(chunk) = reader.get_line()? {
|
||||
out.write_chunk(&chunk)?;
|
||||
}
|
||||
out.flush()?;
|
||||
@@ -1084,7 +1095,7 @@ mod tests {
|
||||
let mut out = OutputBuffer::new(Box::new(out_file));
|
||||
|
||||
// Read the first mmap line ("zero\n") and write it
|
||||
if let Some((chunk, _last_line)) = reader.get_line()? {
|
||||
if let Some(chunk) = reader.get_line()? {
|
||||
out.write_chunk(&chunk)?;
|
||||
}
|
||||
|
||||
@@ -1092,7 +1103,7 @@ mod tests {
|
||||
out.write_str("middle\n")?;
|
||||
|
||||
// Read the second mmap line ("one\n") and write it
|
||||
if let Some((chunk, _last_line)) = reader.get_line()? {
|
||||
if let Some(chunk) = reader.get_line()? {
|
||||
out.write_chunk(&chunk)?;
|
||||
}
|
||||
|
||||
@@ -1137,7 +1148,7 @@ mod tests {
|
||||
// Wrap it in your OutputBuffer and run the loop:
|
||||
let mut out = OutputBuffer::new(Box::new(out_file));
|
||||
let mut nline = 0;
|
||||
while let Some((chunk, _last_line)) = reader.get_line()? {
|
||||
while let Some(chunk) = reader.get_line()? {
|
||||
out.write_chunk(&chunk)?;
|
||||
nline += 1;
|
||||
}
|
||||
@@ -1176,7 +1187,7 @@ mod tests {
|
||||
// Wrap it in your OutputBuffer and run the loop:
|
||||
let mut out = OutputBuffer::new(Box::new(out_file));
|
||||
let mut nline = 0;
|
||||
while let Some((chunk, _last_line)) = reader.get_line()? {
|
||||
while let Some(chunk) = reader.get_line()? {
|
||||
out.write_chunk(&chunk)?;
|
||||
nline += 1;
|
||||
}
|
||||
@@ -1211,7 +1222,7 @@ mod tests {
|
||||
// Wrap it in your OutputBuffer and run the loop:
|
||||
let mut out = OutputBuffer::new(Box::new(out_file));
|
||||
let mut nline = 0;
|
||||
while let Some((chunk, _last_line)) = reader.get_line()? {
|
||||
while let Some(chunk) = reader.get_line()? {
|
||||
out.write_chunk(&chunk)?;
|
||||
nline += 1;
|
||||
}
|
||||
@@ -1246,7 +1257,7 @@ mod tests {
|
||||
// Wrap it in your OutputBuffer and run the loop:
|
||||
let mut out = OutputBuffer::new(Box::new(out_file));
|
||||
let mut nline = 0;
|
||||
while let Some((chunk, _last_line)) = reader.get_line()? {
|
||||
while let Some(chunk) = reader.get_line()? {
|
||||
out.write_chunk(&chunk)?;
|
||||
nline += 1;
|
||||
}
|
||||
@@ -1288,7 +1299,7 @@ mod tests {
|
||||
// Wrap it in OutputBuffer and run the loop:
|
||||
let mut out = OutputBuffer::new(Box::new(out_file));
|
||||
let mut nline_written = 0;
|
||||
while let Some((chunk, _last_line)) = reader.get_line()? {
|
||||
while let Some(chunk) = reader.get_line()? {
|
||||
out.write_chunk(&chunk)?;
|
||||
nline_written += 1;
|
||||
}
|
||||
@@ -1319,52 +1330,46 @@ mod tests {
|
||||
let mut reader = LineReader::open_stream(&path)?;
|
||||
|
||||
// Verify the reader's operation
|
||||
if let Some((
|
||||
IOChunk {
|
||||
content:
|
||||
IOChunkContent::Owned {
|
||||
content,
|
||||
has_newline,
|
||||
..
|
||||
},
|
||||
utf8_verified,
|
||||
..
|
||||
},
|
||||
last_line,
|
||||
)) = reader.get_line()?
|
||||
if let Some(IOChunk {
|
||||
content:
|
||||
IOChunkContent::Owned {
|
||||
content,
|
||||
has_newline,
|
||||
..
|
||||
},
|
||||
utf8_verified,
|
||||
..
|
||||
}) = reader.get_line()?
|
||||
{
|
||||
assert_eq!(content, "first line");
|
||||
assert_eq!(content.len(), 10);
|
||||
assert!(has_newline);
|
||||
assert!(!utf8_verified.get());
|
||||
assert!(!last_line);
|
||||
assert!(!reader.last_line().unwrap());
|
||||
} else {
|
||||
panic!("Expected IOChunkContent::Owned");
|
||||
}
|
||||
|
||||
if let Some((
|
||||
IOChunk {
|
||||
content:
|
||||
IOChunkContent::Owned {
|
||||
content,
|
||||
has_newline,
|
||||
..
|
||||
},
|
||||
..
|
||||
},
|
||||
last_line,
|
||||
)) = reader.get_line()?
|
||||
if let Some(IOChunk {
|
||||
content:
|
||||
IOChunkContent::Owned {
|
||||
content,
|
||||
has_newline,
|
||||
..
|
||||
},
|
||||
..
|
||||
}) = reader.get_line()?
|
||||
{
|
||||
assert_eq!(content, "second line");
|
||||
assert!(has_newline);
|
||||
assert!(!last_line);
|
||||
assert!(!reader.last_line().unwrap());
|
||||
} else {
|
||||
panic!("Expected IOChunkContent::Owned");
|
||||
}
|
||||
|
||||
if let Some((content, last_line)) = reader.get_line()? {
|
||||
if let Some(content) = reader.get_line()? {
|
||||
assert_eq!(content.as_str().unwrap(), "last line");
|
||||
assert!(last_line);
|
||||
assert!(reader.last_line().unwrap());
|
||||
} else {
|
||||
panic!("Expected IOChunk");
|
||||
}
|
||||
@@ -1386,52 +1391,46 @@ mod tests {
|
||||
let mut reader = LineReader::open(&path)?;
|
||||
|
||||
// Verify the reader's operation
|
||||
if let Some((
|
||||
IOChunk {
|
||||
content:
|
||||
IOChunkContent::MmapInput {
|
||||
content, full_span, ..
|
||||
},
|
||||
utf8_verified,
|
||||
..
|
||||
},
|
||||
last_line,
|
||||
)) = reader.get_line()?
|
||||
if let Some(IOChunk {
|
||||
content:
|
||||
IOChunkContent::MmapInput {
|
||||
content, full_span, ..
|
||||
},
|
||||
utf8_verified,
|
||||
..
|
||||
}) = reader.get_line()?
|
||||
{
|
||||
assert_eq!(content, b"first line");
|
||||
assert_eq!(content.len(), 10);
|
||||
assert_eq!(full_span, b"first line\n");
|
||||
assert!(!utf8_verified.get());
|
||||
assert!(!last_line);
|
||||
assert!(!reader.last_line().unwrap());
|
||||
} else {
|
||||
panic!("Expected IOChunkContent::MapInput");
|
||||
}
|
||||
|
||||
if let Some((
|
||||
IOChunk {
|
||||
content:
|
||||
IOChunkContent::MmapInput {
|
||||
content, full_span, ..
|
||||
},
|
||||
utf8_verified,
|
||||
..
|
||||
},
|
||||
last_line,
|
||||
)) = reader.get_line()?
|
||||
if let Some(IOChunk {
|
||||
content:
|
||||
IOChunkContent::MmapInput {
|
||||
content, full_span, ..
|
||||
},
|
||||
utf8_verified,
|
||||
..
|
||||
}) = reader.get_line()?
|
||||
{
|
||||
assert_eq!(content, b"second line");
|
||||
assert_eq!(full_span, b"second line\n");
|
||||
assert!(!utf8_verified.get());
|
||||
assert!(!last_line);
|
||||
assert!(!reader.last_line().unwrap());
|
||||
} else {
|
||||
panic!("Expected IOChunkContent::MapInput");
|
||||
}
|
||||
|
||||
if let Some((content, last_line)) = reader.get_line()? {
|
||||
if let Some(content) = reader.get_line()? {
|
||||
assert_eq!(content.as_bytes(), b"last line");
|
||||
assert_eq!(content.as_str().unwrap(), "last line");
|
||||
assert!(content.utf8_verified.get());
|
||||
assert!(last_line);
|
||||
assert!(reader.last_line().unwrap());
|
||||
// Cached version
|
||||
assert_eq!(content.as_str().unwrap(), "last line");
|
||||
} else {
|
||||
|
||||
@@ -15,12 +15,12 @@ use fancy_regex::{
|
||||
CaptureMatches as FancyCaptureMatches, Captures as FancyCaptures, Regex as FancyRegex,
|
||||
};
|
||||
use memchr::memmem;
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex as RustRegex;
|
||||
use regex::bytes::{
|
||||
CaptureMatches as ByteCaptureMatches, Captures as ByteCaptures, Regex as ByteRegex,
|
||||
};
|
||||
use std::error::Error;
|
||||
use std::sync::LazyLock;
|
||||
use uucore::error::{UResult, USimpleError};
|
||||
|
||||
use crate::sed::fast_io::IOChunk;
|
||||
@@ -32,7 +32,8 @@ use crate::sed::fast_io::IOChunk;
|
||||
// For example, r"\\1" and r"[\1]" will match, whereas only a number
|
||||
// after an odd number of backslashes and outside a character class
|
||||
// should match.
|
||||
static NEEDS_FANCY_RE: Lazy<RustRegex> = Lazy::new(|| regex::Regex::new(r"\\[1-9]").unwrap());
|
||||
static NEEDS_FANCY_RE: LazyLock<RustRegex> =
|
||||
LazyLock::new(|| regex::Regex::new(r"\\[1-9]").unwrap());
|
||||
|
||||
/// All characters signifying that the match must be handled by an RE
|
||||
/// rather than by plain string pattern matching.
|
||||
@@ -41,7 +42,7 @@ static NEEDS_FANCY_RE: Lazy<RustRegex> = Lazy::new(|| regex::Regex::new(r"\\[1-9
|
||||
// matching, because Regex always constructs an automaton and needs
|
||||
// to handle state transitions, whereas plain string matching can
|
||||
// use tailored CPU string or vectored instructions.
|
||||
static NEEDS_RE: Lazy<RustRegex> = Lazy::new(|| {
|
||||
static NEEDS_RE: LazyLock<RustRegex> = LazyLock::new(|| {
|
||||
regex::Regex::new(
|
||||
r"(?x) # Turn on verbose mode
|
||||
( ^ # Non-escaped: i.e. at BOL
|
||||
|
||||
@@ -216,6 +216,7 @@ fn build_context(matches: &ArgMatches) -> ProcessingContext {
|
||||
hold: StringSpace::default(),
|
||||
parsed_block_nesting: 0,
|
||||
label_to_command_map: HashMap::new(),
|
||||
range_commands: Vec::new(),
|
||||
substitution_made: false,
|
||||
append_elements: Vec::new(),
|
||||
}
|
||||
|
||||
+84
-63
@@ -9,8 +9,7 @@
|
||||
// file that was distributed with this source code.
|
||||
|
||||
use crate::sed::command::{
|
||||
Address, AddressType, AddressValue, AppendElement, Command, CommandData, InputAction,
|
||||
ProcessingContext, Transliteration,
|
||||
Address, AppendElement, Command, CommandData, InputAction, ProcessingContext, Transliteration,
|
||||
};
|
||||
use crate::sed::error_handling::{ScriptLocation, input_runtime_error};
|
||||
use crate::sed::fast_io::{IOChunk, LineReader, OutputBuffer};
|
||||
@@ -40,30 +39,21 @@ macro_rules! extract_variant {
|
||||
/// Return true if the passed address matches the current I/O context.
|
||||
fn match_address(
|
||||
addr: &Address,
|
||||
reader: &mut LineReader,
|
||||
pattern: &mut IOChunk,
|
||||
context: &mut ProcessingContext,
|
||||
location: &ScriptLocation,
|
||||
) -> UResult<bool> {
|
||||
match addr.atype {
|
||||
AddressType::Re => {
|
||||
if let AddressValue::Regex(ref re) = addr.value {
|
||||
let regex = re_or_saved_re(re, context, location)?;
|
||||
match regex.is_match(pattern) {
|
||||
Ok(result) => Ok(result),
|
||||
Err(e) => input_runtime_error(location, context, e.to_string()),
|
||||
}
|
||||
} else {
|
||||
Ok(false)
|
||||
match addr {
|
||||
Address::Re(re) => {
|
||||
let regex = re_or_saved_re(re, context, location)?;
|
||||
match regex.is_match(pattern) {
|
||||
Ok(result) => Ok(result),
|
||||
Err(e) => input_runtime_error(location, context, e.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
AddressType::Line => {
|
||||
if let AddressValue::LineNumber(lineno) = addr.value {
|
||||
Ok(context.line_number == lineno)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
Address::Line(lineno) => Ok(context.line_number == *lineno),
|
||||
|
||||
// Recognize "$" as the last line of last file. This is consistent
|
||||
// with the original 7th Research Edition implementation:
|
||||
@@ -71,7 +61,7 @@ fn match_address(
|
||||
// The FreeBSD version checked for subsequent empty files, but this
|
||||
// can lead to destructive reads (e.g. from named pipes),
|
||||
// and is probably an overkill.
|
||||
AddressType::Last => Ok(context.last_line && (context.last_file || context.separate)),
|
||||
Address::Last => Ok(reader.last_line()? && (context.last_file || context.separate)),
|
||||
|
||||
_ => panic!("invalid address type in match_address"),
|
||||
}
|
||||
@@ -81,63 +71,66 @@ fn match_address(
|
||||
/// Return true if the command applies to the given pattern.
|
||||
fn applies(
|
||||
command: &mut Command,
|
||||
reader: &mut LineReader,
|
||||
pattern: &mut IOChunk,
|
||||
context: &mut ProcessingContext,
|
||||
) -> UResult<bool> {
|
||||
let linenum = context.line_number;
|
||||
|
||||
let result = if command.addr1.is_none() && command.addr2.is_none() {
|
||||
// No address
|
||||
Ok(true)
|
||||
} else if let Some(addr2) = &command.addr2 {
|
||||
// Two addresses
|
||||
if let Some(start) = command.start_line {
|
||||
match addr2.atype {
|
||||
AddressType::RelLine => {
|
||||
if let AddressValue::LineNumber(n) = addr2.value {
|
||||
if linenum - start <= n {
|
||||
Ok(true)
|
||||
} else {
|
||||
command.start_line = None;
|
||||
Ok(false)
|
||||
}
|
||||
} else {
|
||||
// Range is already latched active.
|
||||
match addr2 {
|
||||
Address::RelLine(n) => {
|
||||
if linenum - start > *n {
|
||||
command.start_line = None;
|
||||
Ok(false)
|
||||
} else {
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
if match_address(addr2, pattern, context, &command.location)? {
|
||||
Address::Line(n) => {
|
||||
// Special case: already ended
|
||||
if linenum > *n {
|
||||
command.start_line = None;
|
||||
context.last_address = true;
|
||||
Ok(true)
|
||||
} else if addr2.atype == AddressType::Line {
|
||||
if let AddressValue::LineNumber(n) = addr2.value {
|
||||
if linenum > n {
|
||||
command.start_line = None;
|
||||
Ok(false)
|
||||
} else {
|
||||
Ok(true)
|
||||
}
|
||||
} else {
|
||||
Ok(true)
|
||||
}
|
||||
Ok(false)
|
||||
} else {
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
Address::StepMatch(step) => Ok((linenum - start).is_multiple_of(*step)),
|
||||
Address::StepEnd(step) => {
|
||||
// Inclusive end on multiple of step
|
||||
if linenum.is_multiple_of(*step) {
|
||||
command.start_line = None;
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
_ => {
|
||||
if match_address(addr2, reader, pattern, context, &command.location)? {
|
||||
command.start_line = None;
|
||||
context.last_address = true;
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
} else if let Some(addr1) = &command.addr1 {
|
||||
if match_address(addr1, pattern, context, &command.location)? {
|
||||
match addr2.atype {
|
||||
AddressType::Line => {
|
||||
if let AddressValue::LineNumber(n) = addr2.value {
|
||||
if linenum >= n {
|
||||
context.last_address = true;
|
||||
} else {
|
||||
command.start_line = Some(linenum);
|
||||
}
|
||||
// See if latch must start.
|
||||
if match_address(addr1, reader, pattern, context, &command.location)? {
|
||||
match addr2 {
|
||||
Address::Line(n) => {
|
||||
if linenum >= *n {
|
||||
context.last_address = true;
|
||||
} else {
|
||||
command.start_line = Some(linenum);
|
||||
}
|
||||
}
|
||||
AddressType::RelLine => {
|
||||
if let AddressValue::LineNumber(0) = addr2.value {
|
||||
Address::RelLine(n) => {
|
||||
if *n == 0 {
|
||||
context.last_address = true;
|
||||
} else {
|
||||
command.start_line = Some(linenum);
|
||||
@@ -155,9 +148,17 @@ fn applies(
|
||||
Ok(false)
|
||||
}
|
||||
} else if let Some(addr1) = &command.addr1 {
|
||||
Ok(match_address(addr1, pattern, context, &command.location)?)
|
||||
// Single address
|
||||
Ok(match_address(
|
||||
addr1,
|
||||
reader,
|
||||
pattern,
|
||||
context,
|
||||
&command.location,
|
||||
)?)
|
||||
} else {
|
||||
Ok(false)
|
||||
// All allowed cases have been covered by the above logic.
|
||||
panic!("impossible address combination");
|
||||
};
|
||||
|
||||
if command.non_select {
|
||||
@@ -431,8 +432,7 @@ fn process_file(
|
||||
context: &mut ProcessingContext,
|
||||
) -> UResult<()> {
|
||||
// Loop over the input lines as pattern space.
|
||||
'lines: while let Some((mut pattern, last_line)) = reader.get_line()? {
|
||||
context.last_line = last_line;
|
||||
'lines: while let Some(mut pattern) = reader.get_line()? {
|
||||
context.line_number += 1;
|
||||
context.substitution_made = false;
|
||||
// Set the script command from which to start.
|
||||
@@ -455,7 +455,7 @@ fn process_file(
|
||||
while let Some(command_rc) = current.clone() {
|
||||
let mut command = command_rc.borrow_mut();
|
||||
|
||||
if !applies(&mut command, &mut pattern, context)? {
|
||||
if !applies(&mut command, reader, &mut pattern, context)? {
|
||||
// Advance to next command
|
||||
current = command.next.clone();
|
||||
continue;
|
||||
@@ -494,7 +494,7 @@ fn process_file(
|
||||
// At range end replace pattern space with text and
|
||||
// start the next cycle.
|
||||
pattern.clear();
|
||||
if command.addr2.is_none() || context.last_address || context.last_line {
|
||||
if command.addr2.is_none() || context.last_address || reader.last_line()? {
|
||||
let text = extract_variant!(command, Text);
|
||||
output.write_str(text.as_ref())?;
|
||||
}
|
||||
@@ -567,6 +567,10 @@ fn process_file(
|
||||
'p' => {
|
||||
// Write the pattern space to standard output.
|
||||
write_chunk(output, context, &pattern)?;
|
||||
if !pattern.is_newline_terminated() {
|
||||
// !chomped equivalent
|
||||
output.write_str("\n")?; // explicit \n
|
||||
}
|
||||
}
|
||||
'P' => {
|
||||
// Output pattern space, up to the first \n.
|
||||
@@ -675,6 +679,21 @@ fn process_file(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Mark all address ranges non-active (and 0-starting ones as active).
|
||||
fn reset_latched_address_ranges(range_commands: &mut [Rc<RefCell<Command>>]) {
|
||||
for cmd_rc in range_commands.iter() {
|
||||
let mut cmd = cmd_rc.borrow_mut();
|
||||
|
||||
cmd.start_line =
|
||||
// Check for address-spec line 0 pre-latch extension.
|
||||
if let Some(addr1) = &cmd.addr1 && matches!(addr1, Address::Line(0)) {
|
||||
Some(0)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Process all input files
|
||||
pub fn process_all_files(
|
||||
commands: Option<Rc<RefCell<Command>>>,
|
||||
@@ -692,9 +711,11 @@ pub fn process_all_files(
|
||||
.map_err_context(|| format!("error opening input file {}", path.quote()))?;
|
||||
let output = in_place.begin(path)?;
|
||||
|
||||
if context.separate {
|
||||
if index == 0 || context.separate {
|
||||
context.line_number = 0;
|
||||
reset_latched_address_ranges(&mut context.range_commands);
|
||||
}
|
||||
|
||||
context.input_name = path.quote().to_string();
|
||||
process_file(&commands, &mut reader, output, context)?;
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ use std::io::{Read, Write};
|
||||
#[cfg(unix)]
|
||||
use assert_fs::fixture::{FileWriteStr, PathChild};
|
||||
|
||||
use sha2::{Digest, Sha256};
|
||||
use tempfile::NamedTempFile;
|
||||
use uutests::new_ucmd;
|
||||
|
||||
@@ -225,6 +226,15 @@ check_output!(addr_escaped_delimiter, ["-n", "\\_l1\\_7_p", LINES1]);
|
||||
check_output!(addr_range_numeric, ["-n", "1,4p", LINES1]);
|
||||
check_output!(addr_range_to_last, ["-n", "1,$p", LINES1, LINES2]);
|
||||
check_output!(addr_range_to_pattern, ["-n", "1,/l2_9/p", LINES1, LINES2]);
|
||||
check_output!(
|
||||
addr_range_straddle,
|
||||
["-n", "/l1_3/,/l2_3/p", LINES1, LINES2]
|
||||
);
|
||||
check_output!(
|
||||
addr_range_separate,
|
||||
["-n", "--separate", "/l1_3/,/l2_3/p", LINES1, LINES2]
|
||||
);
|
||||
check_output!(addr_range_from_zero_to_pattern, ["-n", "0,/_1/p", LINES1]);
|
||||
check_output!(addr_pattern_to_last, ["-n", "/4/,$p", LINES1, LINES2]);
|
||||
check_output!(addr_pattern_to_straddle, ["-n", "/4/,20p", LINES1, LINES2]);
|
||||
check_output!(addr_pattern_to_pattern, ["-n", "/4/,/10/p", LINES1, LINES2]);
|
||||
@@ -259,6 +269,10 @@ check_output!(
|
||||
);
|
||||
check_output!(addr_empty_re_reuse, ["-n", "/_2/,//p", LINES1, LINES2]);
|
||||
check_output!(addr_simple_negation, ["-e", r"4,12!s/^/^/", LINES1]);
|
||||
check_output!(addr_range_even, ["-n", "0~2p", LINES1]);
|
||||
check_output!(addr_range_odd, ["-n", "1~2p", LINES1]);
|
||||
check_output!(addr_range_step_zero, ["-n", "10~0p", LINES1]);
|
||||
check_output!(addr_range_end_multiple, ["-n", "/l1_2/,~10p", LINES1]);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Substitution: s
|
||||
@@ -987,6 +1001,42 @@ check_output!(pi, ["-f", "script/math.sed", "input/pi"]);
|
||||
// Solve the Towers of Hanoi puzzle
|
||||
check_output!(hanoi, ["-f", "script/hanoi.sed", "input/hanoi"]);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Long-running scripts
|
||||
// Test with cargo test -- --ignored
|
||||
|
||||
// Check the output of Bach's prelude in C major from WTC book I.
|
||||
// Run with cargo test test_bach_prelude_matches -- --ignored.
|
||||
#[test]
|
||||
#[ignore] // Slow; produces 5.8 MB of raw audio.
|
||||
fn test_bach_prelude_matches() {
|
||||
let res = new_ucmd!()
|
||||
.args(&["-E", "-f", "script/bach.sed"])
|
||||
.pipe_in("\n")
|
||||
.succeeds();
|
||||
|
||||
// Compare SHA-256 output against GNU sed output.
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(res.stdout());
|
||||
let digest = hasher.finalize();
|
||||
|
||||
let got = hex::encode(digest);
|
||||
assert_eq!(
|
||||
got,
|
||||
"c4e50d6791a60692745e958dc48d43a40bccea2ce5cea31b7125a40604cc3219"
|
||||
);
|
||||
}
|
||||
|
||||
// Draw the Mandelbrot set.
|
||||
#[ignore] // Slow; takes > 15" on an i7 CPU
|
||||
#[test]
|
||||
fn test_mandelbrod() {
|
||||
new_ucmd!()
|
||||
.args(&["-En", "-f", "script/mandelbrot.sed", "input/newline"])
|
||||
.succeeds()
|
||||
.stdout_is_fixture("output/mandelbrot");
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Error handling
|
||||
#[test]
|
||||
@@ -1016,6 +1066,51 @@ fn test_undefined_label() {
|
||||
.stderr_is("sed: <script argument 1>:1:1: error: undefined label `foo'\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_addr0_non_posix() {
|
||||
new_ucmd!()
|
||||
.args(&["--posix", "0,/foo/p"])
|
||||
.fails()
|
||||
.code_is(1)
|
||||
.stderr_is("sed: <script argument 1>:1:2: error: address 0 is invalid in POSIX mode\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_addr0_second_required() {
|
||||
new_ucmd!()
|
||||
.args(&["0p"])
|
||||
.fails()
|
||||
.code_is(1)
|
||||
.stderr_is("sed: <script argument 1>:1:2: error: address 0 requires a second address\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_addr0_second_re_only() {
|
||||
new_ucmd!()
|
||||
.args(&["0,4p"])
|
||||
.fails()
|
||||
.code_is(1)
|
||||
.stderr_is("sed: <script argument 1>:1:4: error: address 0 can only be used with a regular expression or ~step\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_step_match_non_posix() {
|
||||
new_ucmd!()
|
||||
.args(&["--posix", "3~2p"])
|
||||
.fails()
|
||||
.code_is(1)
|
||||
.stderr_is("sed: <script argument 1>:1:3: error: ~step is invalid in POSIX mode\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_step_end_non_posix() {
|
||||
new_ucmd!()
|
||||
.args(&["--posix", "3,~2p"])
|
||||
.fails()
|
||||
.code_is(1)
|
||||
.stderr_is("sed: <script argument 1>:1:4: error: ~step is invalid in POSIX mode\n");
|
||||
}
|
||||
|
||||
// The following test diverse ways in which regexes are matched.
|
||||
// Search for 'regex\.' to find them in the code.
|
||||
#[test]
|
||||
@@ -1080,3 +1175,14 @@ fn test_missing_address_re() {
|
||||
.code_is(2)
|
||||
.stderr_is("sed: <script argument 1>:2:3: 'input/lines1':1 error: no previous regular expression\n");
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Test for issue #143: Missing newline in output with `-e p`
|
||||
#[test]
|
||||
fn test_print_command_adds_newline() {
|
||||
new_ucmd!()
|
||||
.args(&["-e", "p"])
|
||||
.pipe_in("foo")
|
||||
.succeeds()
|
||||
.stdout_is("foo\nfoo");
|
||||
}
|
||||
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
l1_2
|
||||
l1_3
|
||||
l1_4
|
||||
l1_5
|
||||
l1_6
|
||||
l1_7
|
||||
l1_8
|
||||
l1_9
|
||||
l1_10
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
l1_2
|
||||
l1_4
|
||||
l1_6
|
||||
l1_8
|
||||
l1_10
|
||||
l1_12
|
||||
l1_14
|
||||
@@ -0,0 +1 @@
|
||||
l1_1
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user