Compare commits
No commits in common. "v0.1.0" and "main" have entirely different histories.
22 changed files with 813 additions and 471 deletions
52
.forgejo/workflows/push-next.yml
Normal file
52
.forgejo/workflows/push-next.yml
Normal file
|
@ -0,0 +1,52 @@
|
|||
name: Rust
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["next"]
|
||||
pull_request:
|
||||
branches: ["main"]
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: docker
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
toolchain:
|
||||
- name: stable
|
||||
- name: nightly
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Format
|
||||
uses: https://git.kemitix.net/kemitix/rust@v2.3.0
|
||||
with:
|
||||
args: ${{ matrix.toolchain.name }} cargo fmt --all -- --check
|
||||
|
||||
# - name: Machete
|
||||
# uses: https://github.com/bnjbvr/cargo-machete@v0.6.2
|
||||
|
||||
- name: Clippy
|
||||
uses: https://git.kemitix.net/kemitix/rust@v2.3.0
|
||||
with:
|
||||
args: ${{ matrix.toolchain.name }} cargo clippy
|
||||
|
||||
- name: Build
|
||||
uses: https://git.kemitix.net/kemitix/rust@v2.3.0
|
||||
with:
|
||||
args: ${{ matrix.toolchain.name }} cargo build
|
||||
|
||||
- name: Test
|
||||
uses: https://git.kemitix.net/kemitix/rust@v2.3.0
|
||||
with:
|
||||
args: ${{ matrix.toolchain.name }} cargo test --no-fail-fast
|
||||
|
||||
- name: Integration
|
||||
uses: https://git.kemitix.net/kemitix/rust@v2.3.0
|
||||
with:
|
||||
args: ./test.sh
|
6
.gitattributes
vendored
6
.gitattributes
vendored
|
@ -1,6 +0,0 @@
|
|||
* text=auto
|
||||
*.zig text eol=lf
|
||||
zig.mod text eol=lf
|
||||
zigmod.* text eol=lf
|
||||
zig.mod linguist-language=YAML
|
||||
zig.mod gitlab-language=yaml
|
27
.github/workflows/main.yml
vendored
27
.github/workflows/main.yml
vendored
|
@ -1,27 +0,0 @@
|
|||
name: CI
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
workflow_dispatch:
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
zig: [ 0.9.0, master]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Setup Zig
|
||||
uses: goto-bus-stop/setup-zig@v1.3.0
|
||||
with:
|
||||
version: ${{ matrix.zig }}
|
||||
- run: zig version
|
||||
- run: zig env
|
||||
- uses: nektro/actions-setup-zigmod@v1
|
||||
- run: zigmod ci
|
||||
- run: zig build test
|
||||
- run: zig build
|
||||
- run: cp zig-out/bin/skip .
|
||||
- run: ./test.sh
|
10
.gitignore
vendored
10
.gitignore
vendored
|
@ -1,7 +1,3 @@
|
|||
zig-cache/
|
||||
zig-out/
|
||||
.zigmod
|
||||
deps.zig
|
||||
/test.in
|
||||
/test.out
|
||||
/test.expect
|
||||
/target
|
||||
dist/
|
||||
input.txt
|
||||
|
|
46
.woodpecker.yml
Normal file
46
.woodpecker.yml
Normal file
|
@ -0,0 +1,46 @@
|
|||
variables:
|
||||
- &rust_image "docker.io/rust:1.82.0"
|
||||
- &slow_check_paths
|
||||
- path:
|
||||
# rust source code
|
||||
- "crates/**"
|
||||
- "src/**"
|
||||
- "tests/**"
|
||||
- "**/Cargo.toml"
|
||||
- "Cargo.lock"
|
||||
# database migrations
|
||||
- "migrations/**"
|
||||
# config files and scripts used by ci
|
||||
- ".woodpecker.yml"
|
||||
|
||||
steps:
|
||||
toml_fmt:
|
||||
image: docker.io/tamasfe/taplo:0.9.3
|
||||
commands:
|
||||
- taplo format --check
|
||||
|
||||
cargo_machete:
|
||||
image: *rust_image
|
||||
commands:
|
||||
- wget https://github.com/cargo-bins/cargo-binstall/releases/latest/download/cargo-binstall-x86_64-unknown-linux-musl.tgz
|
||||
- tar -xvf cargo-binstall-x86_64-unknown-linux-musl.tgz
|
||||
- rm cargo-binstall-x86_64-unknown-linux-musl.tgz
|
||||
- mv cargo-binstall /usr/local/cargo/bin
|
||||
- cargo binstall -y cargo-machete
|
||||
- cargo machete
|
||||
|
||||
ignored_files:
|
||||
image: docker.io/alpine:latest
|
||||
commands:
|
||||
- apk add git
|
||||
- IGNORED=$(git ls-files --cached -i --exclude-standard)
|
||||
- if [[ "$IGNORED" ]]; then echo "Ignored files present:\n$IGNORED\n"; exit 1; fi
|
||||
|
||||
publish_to_crates_io:
|
||||
image: *rust_image
|
||||
commands:
|
||||
- cargo login "$CARGO_REGISTRY_TOKEN"
|
||||
- cargo publish --registry crates-io --no-verify
|
||||
secrets: [cargo_registry_token]
|
||||
when:
|
||||
event: tag
|
223
Cargo.lock
generated
Normal file
223
Cargo.lock
generated
Normal file
|
@ -0,0 +1,223 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
"anstyle-query",
|
||||
"anstyle-wincon",
|
||||
"colorchoice",
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c"
|
||||
dependencies = [
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-query"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-wincon"
|
||||
version = "3.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "80c21025abd42669a92efc996ef13cfb2c5c627858421ea58d5c3b331a6c134f"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "458bf1f341769dfcf849846f65dffdf9146daa56bcd2a47cb4e1de9915567c99"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
"strsim",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce"
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.78"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "skip"
|
||||
version = "0.2.1"
|
||||
dependencies = [
|
||||
"clap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "79d9531f94112cfc3e4c8f5f02cb2b58f72c97b7efd85f70203cc6d8efda5927"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
|
17
Cargo.toml
Normal file
17
Cargo.toml
Normal file
|
@ -0,0 +1,17 @@
|
|||
[package]
|
||||
name = "skip"
|
||||
version = "0.2.1"
|
||||
edition = "2021"
|
||||
authors = ["Paul Campbell <pcampbell@kemitix.net>"]
|
||||
categories = ["command-line-utilities"]
|
||||
description = "Skip lines in a file"
|
||||
license = "MIT"
|
||||
repository = "https://git.kemitix.net/kemitix/skip"
|
||||
keywords = ["skip", "lines", "file", "text", "utility"]
|
||||
rust-version = "1.74.1"
|
||||
exclude = [".cargo_home"]
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "4.5", features = ["derive"] }
|
21
LICENSE
21
LICENSE
|
@ -1,21 +0,0 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2021 Paul Campbell
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
99
README.md
99
README.md
|
@ -2,34 +2,29 @@
|
|||
|
||||
Skip part of a file.
|
||||
|
||||
![GitHub release (latest by date)](
|
||||
https://img.shields.io/github/v/release/kemitix/skip?style=for-the-badge)
|
||||
![GitHub Release Date](
|
||||
https://img.shields.io/github/release-date/kemitix/skip?style=for-the-badge)
|
||||
|
||||
As `head` will show the top of a file after a number of line,
|
||||
As `head` will show the top of a file up-to a number of line,
|
||||
so `skip` will do the opposite, and not show the top of the file,
|
||||
but will show the rest.
|
||||
|
||||
Additionally, it can check for whole lines matching,
|
||||
or for a token being present on the line.
|
||||
|
||||
N.B.: The `skip` crate used to be an implementation of [Skip list](https://en.wikipedia.org/wiki/Skip_list),
|
||||
by [Luo Jia / Zhouqi Jiang](https://github.com/luojia65) ([source](https://github.com/luojia65/skip)).
|
||||
That crate will be republished as [skip-list](https://crates.io/crates/skip-list) (soon).
|
||||
|
||||
## Usage
|
||||
|
||||
### Skip a fixed number of lines
|
||||
|
||||
This example reads the file from stdin.
|
||||
|
||||
File: `input.txt`
|
||||
|
||||
```text
|
||||
line 1
|
||||
```bash
|
||||
echo "line 1
|
||||
line 2
|
||||
line 3
|
||||
line 4
|
||||
```
|
||||
line 4" > input.txt
|
||||
|
||||
```bash
|
||||
skip 2 < input.txt
|
||||
```
|
||||
|
||||
|
@ -42,20 +37,18 @@ line 4
|
|||
|
||||
### Skip until a number of matching lines
|
||||
|
||||
The whole line must match.
|
||||
|
||||
This example reads the named file.
|
||||
|
||||
File: `input.txt`
|
||||
|
||||
```text
|
||||
alpha
|
||||
```bash
|
||||
echo "alpha
|
||||
beta
|
||||
alpha
|
||||
alpha
|
||||
gamma
|
||||
alpha
|
||||
```
|
||||
alpha" > input.txt
|
||||
|
||||
```bash
|
||||
skip 2 --line alpha input.txt
|
||||
```
|
||||
|
||||
|
@ -67,24 +60,22 @@ gamma
|
|||
alpha
|
||||
```
|
||||
|
||||
### Skip lines until a number of tokens as seen
|
||||
### Skip lines until a number of tokens are seen
|
||||
|
||||
Looks for a string within a line, counting each occurance.
|
||||
|
||||
This example reads the file from stdin.
|
||||
|
||||
File: `input.txt`
|
||||
|
||||
```text
|
||||
Lorem ipsum dolor sit amet,
|
||||
```bash
|
||||
echo "Lorem ipsum dolor sit amet,
|
||||
consectetur adipiscing elit,
|
||||
sed do eiusmod tempor incididunt
|
||||
ut labore et dolore magna aliqua.
|
||||
Ut enim ad minim veniam,
|
||||
quis nostrud exercitation ullamco
|
||||
laboris nisi ut aliquip ex ea
|
||||
commodo consequat.
|
||||
```
|
||||
commodo consequat." > input.txt
|
||||
|
||||
```bash
|
||||
cat input.txt | skip 2 --token dolor
|
||||
```
|
||||
|
||||
|
@ -99,3 +90,55 @@ commodo consequat.
|
|||
|
||||
It matches the first `dolor` on line 1,
|
||||
and the second on line 4 as part of the word `dolore`.
|
||||
|
||||
### Skip lines until a lines with tokens are seen
|
||||
|
||||
Looks for a string within a line, only counting each matching line once.
|
||||
|
||||
This example reads the file from stdin.
|
||||
|
||||
```bash
|
||||
echo "Lorem ipsum dolor sit amet,
|
||||
consectetur adipiscing elit,
|
||||
sed do eiusmod tempor incididunt
|
||||
ut labore et dolore magna aliqua.
|
||||
Ut enim ad minim veniam,
|
||||
quis nostrud exercitation ullamco
|
||||
laboris nisi ut aliquip ex ea
|
||||
commodo consequat." > input.txt
|
||||
|
||||
cat input.txt | skip 4 --token m --ignore-extras
|
||||
```
|
||||
|
||||
Will output:
|
||||
|
||||
```text
|
||||
quis nostrud exercitation ullamco
|
||||
laboris nisi ut aliquip ex ea
|
||||
commodo consequat.
|
||||
```
|
||||
|
||||
Without `--ignore-extras`, it would have found the fourth `m` on line 3.
|
||||
|
||||
```bash
|
||||
echo "Lorem ipsum dolor sit amet,
|
||||
consectetur adipiscing elit,
|
||||
sed do eiusmod tempor incididunt
|
||||
ut labore et dolore magna aliqua.
|
||||
Ut enim ad minim veniam,
|
||||
quis nostrud exercitation ullamco
|
||||
laboris nisi ut aliquip ex ea
|
||||
commodo consequat." > input.txt
|
||||
|
||||
cat input.txt | skip 4 --token m
|
||||
```
|
||||
|
||||
Outputing:
|
||||
|
||||
```text
|
||||
ut labore et dolore magna aliqua.
|
||||
Ut enim ad minim veniam,
|
||||
quis nostrud exercitation ullamco
|
||||
laboris nisi ut aliquip ex ea
|
||||
commodo consequat.
|
||||
```
|
||||
|
|
35
build.zig
35
build.zig
|
@ -1,35 +0,0 @@
|
|||
const std = @import("std");
|
||||
const deps = @import("./deps.zig");
|
||||
|
||||
pub fn build(b: *std.build.Builder) void {
|
||||
// Standard target options allows the person running `zig build` to choose
|
||||
// what target to build for. Here we do not override the defaults, which
|
||||
// means any target is allowed, and the default is native. Other options
|
||||
// for restricting supported target set are available.
|
||||
const target = b.standardTargetOptions(.{});
|
||||
|
||||
// Standard release options allow the person running `zig build` to select
|
||||
// between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall.
|
||||
const mode = b.standardReleaseOptions();
|
||||
|
||||
const exe = b.addExecutable("skip", "src/main.zig");
|
||||
exe.setTarget(target);
|
||||
exe.setBuildMode(mode);
|
||||
deps.addAllTo(exe);
|
||||
exe.install();
|
||||
|
||||
const run_cmd = exe.run();
|
||||
run_cmd.step.dependOn(b.getInstallStep());
|
||||
if (b.args) |args| {
|
||||
run_cmd.addArgs(args);
|
||||
}
|
||||
|
||||
const run_step = b.step("run", "Run the app");
|
||||
run_step.dependOn(&run_cmd.step);
|
||||
|
||||
const exe_tests = b.addTest("src/main.zig");
|
||||
exe_tests.setBuildMode(mode);
|
||||
|
||||
const test_step = b.step("test", "Run unit tests");
|
||||
test_step.dependOn(&exe_tests.step);
|
||||
}
|
15
justfile
Normal file
15
justfile
Normal file
|
@ -0,0 +1,15 @@
|
|||
dist: target-release
|
||||
if test ! -d dist ; then mkdir dist ; fi
|
||||
cp target/release/skip dist/
|
||||
|
||||
target-release: unittest inttest
|
||||
cargo build --release
|
||||
|
||||
inttest: target-debug
|
||||
./test.sh
|
||||
|
||||
unittest: target-debug
|
||||
cargo test
|
||||
|
||||
target-debug:
|
||||
cargo build
|
10
renovate.json
Normal file
10
renovate.json
Normal file
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": ["config:recommended"],
|
||||
"packageRules": [
|
||||
{
|
||||
"matchManagers": ["cargo"],
|
||||
"rangeStrategy": "replace"
|
||||
}
|
||||
]
|
||||
}
|
263
src/lib.rs
Normal file
263
src/lib.rs
Normal file
|
@ -0,0 +1,263 @@
|
|||
use clap::Parser;
|
||||
use std::fmt::Debug;
|
||||
use std::fs::File;
|
||||
use std::io::{BufRead, BufReader, Write};
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
|
||||
|
||||
/// Skip lines at the start of a file
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(version)]
|
||||
pub struct Cli {
|
||||
/// The number of lines (or tokens) to skip
|
||||
lines: usize,
|
||||
/// The file to read, or stdin if not given
|
||||
pub file: Option<PathBuf>,
|
||||
/// Skip until N lines matching this
|
||||
#[arg(short, long, conflicts_with = "token")]
|
||||
line: Option<String>,
|
||||
/// Skip lines until N tokens found
|
||||
#[arg(
|
||||
short,
|
||||
long,
|
||||
conflicts_with = "line",
|
||||
required_if_eq("ignore_extras", "true")
|
||||
)]
|
||||
token: Option<String>,
|
||||
/// Only count the first token on each line
|
||||
#[arg(short, long = "ignore-extras")]
|
||||
ignore_extras: bool,
|
||||
}
|
||||
|
||||
pub fn skip(cli: &Cli, writer: &mut impl Write) -> Result<()> {
|
||||
let reader: Box<dyn BufRead> = match &cli.file {
|
||||
Some(ref file) => {
|
||||
let file = File::open(file)?;
|
||||
Box::new(BufReader::new(file))
|
||||
}
|
||||
None => Box::new(BufReader::new(std::io::stdin())),
|
||||
};
|
||||
if let Some(line) = &cli.line {
|
||||
skip_lines_matching(cli, reader, writer, line)
|
||||
} else if let Some(ref token) = cli.token {
|
||||
skip_tokens(cli, reader, writer, token)
|
||||
} else {
|
||||
skip_lines(cli, reader, writer)
|
||||
}
|
||||
}
|
||||
|
||||
// skip a number of lines
|
||||
fn skip_lines(cli: &Cli, reader: Box<dyn BufRead>, writer: &mut impl Write) -> Result<()> {
|
||||
for (counter, current_line) in reader.lines().map_while(Option::Some).flatten().enumerate() {
|
||||
if counter >= cli.lines {
|
||||
writeln!(writer, "{}", current_line)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// skip until a number of matching lines seen
|
||||
fn skip_lines_matching(
|
||||
cli: &Cli,
|
||||
reader: Box<dyn BufRead>,
|
||||
writer: &mut impl Write,
|
||||
line: &str,
|
||||
) -> Result<()> {
|
||||
let mut counter = 0usize;
|
||||
for current_line in reader.lines().map_while(Option::Some).flatten() {
|
||||
if counter >= cli.lines {
|
||||
writeln!(writer, "{}", current_line)?;
|
||||
}
|
||||
if line == current_line {
|
||||
counter += 1;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// skip until a number of tokens seen
|
||||
// may or may not count each occurance of token on a line - see cli.ignore_extras
|
||||
fn skip_tokens(
|
||||
cli: &Cli,
|
||||
reader: Box<dyn BufRead>,
|
||||
writer: &mut impl Write,
|
||||
token: &str,
|
||||
) -> Result<()> {
|
||||
let mut counter = 0usize;
|
||||
|
||||
for current_line in reader.lines().map_while(Option::Some).flatten() {
|
||||
if counter >= cli.lines {
|
||||
writeln!(writer, "{}", current_line)?;
|
||||
}
|
||||
if current_line.contains(token) {
|
||||
if cli.ignore_extras {
|
||||
counter += 1;
|
||||
} else {
|
||||
let occurances = current_line.matches(&token).count();
|
||||
counter += occurances;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn skip_one_line() -> Result<()> {
|
||||
//given
|
||||
let cli = Cli {
|
||||
lines: 1,
|
||||
file: Some(PathBuf::from("tests/two-lines.txt")),
|
||||
line: None,
|
||||
token: None,
|
||||
ignore_extras: false,
|
||||
};
|
||||
let mut lines = Vec::new();
|
||||
|
||||
//when
|
||||
skip(&cli, &mut lines)?;
|
||||
|
||||
//then
|
||||
assert_eq!(String::from_utf8(lines)?, "line 2\n");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn skip_two_lines() -> Result<()> {
|
||||
//given
|
||||
let cli = Cli {
|
||||
lines: 2,
|
||||
file: Some(PathBuf::from("tests/four-lines.txt")),
|
||||
line: None,
|
||||
token: None,
|
||||
ignore_extras: false,
|
||||
};
|
||||
let mut lines = Vec::new();
|
||||
|
||||
//when
|
||||
skip(&cli, &mut lines)?;
|
||||
|
||||
//then
|
||||
assert_eq!(String::from_utf8(lines)?, ["alpha", "gamma\n"].join("\n"));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn skip_two_matching_lines() -> Result<()> {
|
||||
//given
|
||||
let cli = Cli {
|
||||
lines: 2,
|
||||
file: Some(PathBuf::from("tests/four-lines.txt")),
|
||||
line: Some(String::from("alpha")),
|
||||
token: None,
|
||||
ignore_extras: false,
|
||||
};
|
||||
let mut lines = Vec::new();
|
||||
|
||||
//when
|
||||
skip(&cli, &mut lines)?;
|
||||
|
||||
//then
|
||||
assert_eq!(String::from_utf8(lines)?, "gamma\n");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn skip_three_matching_tokens() -> Result<()> {
|
||||
//given
|
||||
let cli = Cli {
|
||||
lines: 3,
|
||||
file: Some(PathBuf::from("tests/poem.txt")),
|
||||
line: None,
|
||||
token: Some(String::from("one")),
|
||||
ignore_extras: false,
|
||||
};
|
||||
let mut lines = Vec::new();
|
||||
|
||||
//when
|
||||
skip(&cli, &mut lines)?;
|
||||
|
||||
//then
|
||||
assert_eq!(
|
||||
String::from_utf8(lines)?,
|
||||
[
|
||||
"Or help one fainting robin",
|
||||
"Unto his nest again,",
|
||||
"I shall not live in vain.\n"
|
||||
]
|
||||
.join("\n")
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn skip_three_matching_tokens_include_extras() -> Result<()> {
|
||||
//given
|
||||
let cli = Cli {
|
||||
lines: 4,
|
||||
file: Some(PathBuf::from("tests/lorem.txt")),
|
||||
line: None,
|
||||
token: Some(String::from("or")),
|
||||
ignore_extras: false,
|
||||
};
|
||||
let mut lines = Vec::new();
|
||||
|
||||
//when
|
||||
skip(&cli, &mut lines)?;
|
||||
|
||||
//then
|
||||
assert_eq!(
|
||||
String::from_utf8(lines)?,
|
||||
[
|
||||
//Lorem ipsum dolor sit amet, -- +2 = 2
|
||||
//consectetur adipiscing elit,
|
||||
//sed do eiusmod tempor incididunt -- +1 = 3
|
||||
//ut labore et dolore magna aliqua. -- +2 = 5
|
||||
"Ut enim ad minim veniam,",
|
||||
"quis nostrud exercitation ullamco",
|
||||
"laboris nisi ut aliquip ex ea",
|
||||
"commodo consequat.\n"
|
||||
]
|
||||
.join("\n")
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn skip_three_matching_tokens_ignore_extras() -> Result<()> {
|
||||
//given
|
||||
let cli = Cli {
|
||||
lines: 4,
|
||||
file: Some(PathBuf::from("tests/lorem.txt")),
|
||||
line: None,
|
||||
token: Some(String::from("or")),
|
||||
ignore_extras: true,
|
||||
};
|
||||
let mut lines = Vec::new();
|
||||
|
||||
//when
|
||||
skip(&cli, &mut lines)?;
|
||||
|
||||
//then
|
||||
assert_eq!(
|
||||
String::from_utf8(lines)?,
|
||||
[
|
||||
//Lorem ipsum dolor sit amet, -- 1
|
||||
//consectetur adipiscing elit,
|
||||
//sed do eiusmod tempor incididunt -- 2
|
||||
//ut labore et dolore magna aliqua. -- 3
|
||||
//Ut enim ad minim veniam,
|
||||
//quis nostrud exercitation ullamco
|
||||
//laboris nisi ut aliquip ex ea -- 4
|
||||
"commodo consequat.\n"
|
||||
]
|
||||
.join("\n")
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
8
src/main.rs
Normal file
8
src/main.rs
Normal file
|
@ -0,0 +1,8 @@
|
|||
use clap::Parser;
|
||||
use skip::{skip, Cli, Result};
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let cli = Cli::parse();
|
||||
let mut stdout = std::io::stdout();
|
||||
skip(&cli, &mut stdout)
|
||||
}
|
321
src/main.zig
321
src/main.zig
|
@ -1,321 +0,0 @@
|
|||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
const os = builtin.os;
|
||||
const fmt = std.fmt;
|
||||
const mem = std.mem;
|
||||
const testing = std.testing;
|
||||
const io = std.io;
|
||||
const heap = std.heap;
|
||||
const fs = std.fs;
|
||||
const clap = @import("clap");
|
||||
|
||||
const version = "0.1.0";
|
||||
|
||||
// step 1: [x] read in a file from stdin and write out to stdout
|
||||
// step 2: [x] read in a named file in parameters and write out to stdout
|
||||
// step 3: [x] skip a number of lines
|
||||
// step 4: [ ] skip a number of matching lines
|
||||
// step 5: [ ] skip a number of tokens
|
||||
|
||||
const maxLineLength = 4096;
|
||||
|
||||
pub fn main() anyerror!void {
|
||||
var buffer: [maxLineLength]u8 = undefined;
|
||||
var fba = heap.FixedBufferAllocator.init(&buffer);
|
||||
const allocator = fba.allocator();
|
||||
|
||||
const config: Config = parseArgs(allocator) catch |err| switch (err) {
|
||||
error.EarlyExit => return,
|
||||
error.FileNotFound => return,
|
||||
error.BadArgs => return,
|
||||
else => @panic("Unknown error"),
|
||||
};
|
||||
defer config.deinit();
|
||||
|
||||
const stdout = io.getStdOut();
|
||||
if (config.file) |file| {
|
||||
try dumpInput(config, file, stdout, allocator);
|
||||
} else {
|
||||
const stdin = io.getStdIn();
|
||||
try dumpInput(config, stdin, stdout, allocator);
|
||||
}
|
||||
}
|
||||
|
||||
const errors = error {
|
||||
EarlyExit,
|
||||
FileNotFound,
|
||||
BadArgs,
|
||||
};
|
||||
|
||||
const Config = struct {
|
||||
lines: u32,
|
||||
file: ?fs.File,
|
||||
line: ?[]const u8 = null,
|
||||
token: ?[]const u8 = null,
|
||||
|
||||
pub fn deinit(self: @This()) void {
|
||||
if (self.file) |f| {
|
||||
f.close();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
fn parseArgs(allocator: mem.Allocator) !Config {
|
||||
const params = comptime [_]clap.Param(clap.Help) {
|
||||
clap.parseParam("<N> The number of lines to skip") catch unreachable,
|
||||
clap.parseParam("[<FILE>] The file to read or stdin if not given") catch unreachable,
|
||||
clap.parseParam("-l, --line <STR> Skip until N lines matching this") catch unreachable,
|
||||
clap.parseParam("-t, --token <STR> Skip lines until N tokens found") catch unreachable,
|
||||
clap.parseParam("-h, --help Display this help and exit") catch unreachable,
|
||||
clap.parseParam("-v, --version Display the version") catch unreachable,
|
||||
};
|
||||
var diag = clap.Diagnostic{};
|
||||
var args = clap.parse(clap.Help, ¶ms, .{ .diagnostic = &diag }) catch |err| {
|
||||
diag.report(io.getStdErr().writer(), err) catch {};
|
||||
return err;
|
||||
};
|
||||
defer args.deinit();
|
||||
|
||||
if (args.flag("--version")) {
|
||||
std.debug.print("skip version {s}\n", .{ version });
|
||||
return error.EarlyExit;
|
||||
}
|
||||
if (args.flag("--help")) {
|
||||
try io.getStdErr().writer().print("skip <N> [<FILE>] [-h|--help] [-v|--version]\n", .{});
|
||||
try clap.help(io.getStdErr().writer(), ¶ms);
|
||||
return error.EarlyExit;
|
||||
}
|
||||
|
||||
if (args.option("--line")) |_| {
|
||||
if (args.option("--token")) |_| {
|
||||
try io.getStdErr().writer().print("Error: only specify one of --line or --token, not both\n", .{});
|
||||
return error.BadArgs;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var line: ?[]const u8 = null;
|
||||
if (args.option("--line")) |match| {
|
||||
line = try allocator.dupe(u8, match);
|
||||
}
|
||||
|
||||
var token: ?[]const u8 = null;
|
||||
if (args.option("--token")) |match| {
|
||||
token = try allocator.dupe(u8, match);
|
||||
}
|
||||
|
||||
var n: u32 = 0;
|
||||
var file: ?fs.File = null;
|
||||
if (args.positionals().len == 0) {
|
||||
std.debug.print("Number of lines to skip not given. Try skip --help\n", .{});
|
||||
return error.EarlyExit;
|
||||
}
|
||||
if (args.positionals().len >= 1) {
|
||||
n = try fmt.parseInt(u32, args.positionals()[0], 10);
|
||||
}
|
||||
if (args.positionals().len >= 2) {
|
||||
const filename = args.positionals()[1];
|
||||
file = fs.cwd().openFile(filename, .{ .read = true, .write = false }) catch |err| switch (err) {
|
||||
error.FileNotFound => {
|
||||
try io.getStdErr().writer().print("Error: File not found: {s}\n", .{ filename });
|
||||
return err;
|
||||
},
|
||||
else => return err,
|
||||
};
|
||||
}
|
||||
return Config {
|
||||
.lines = n,
|
||||
.file = file,
|
||||
.line = line,
|
||||
.token = token,
|
||||
};
|
||||
}
|
||||
|
||||
fn dumpInput(config: Config, in: fs.File, out: fs.File, allocator: mem.Allocator) !void {
|
||||
const writer = out.writer();
|
||||
const reader = in.reader();
|
||||
var it: LineIterator = lineIterator(reader, allocator);
|
||||
var c: usize = 0;
|
||||
while (c < config.lines) {
|
||||
const line = it.next();
|
||||
if (config.line) |match| {
|
||||
if (line) |memory| {
|
||||
if (mem.eql(u8, match, memory)) {
|
||||
c += 1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (config.token) |token| {
|
||||
if (line) |memory| {
|
||||
c += mem.count(u8, memory, token);
|
||||
}
|
||||
} else {
|
||||
c += 1;
|
||||
}
|
||||
}
|
||||
if (line) |memory| {
|
||||
allocator.free(memory);
|
||||
} else return;
|
||||
}
|
||||
try pumpIterator(&it, writer, allocator);
|
||||
}
|
||||
|
||||
test "dumpInput skip 1 line" {
|
||||
const file = try fs.cwd().openFile("src/test/two-lines.txt", .{ .read = true, .write = false });
|
||||
defer file.close();
|
||||
|
||||
const tempFile = "zig-cache/test.txt";
|
||||
|
||||
const output = try fs.cwd().createFile(tempFile, .{});
|
||||
defer output.close();
|
||||
|
||||
const config = Config{
|
||||
.lines = 1,
|
||||
.file = file,
|
||||
};
|
||||
|
||||
try dumpInput(config, file, output, testing.allocator);
|
||||
|
||||
const result = try fs.cwd().openFile(tempFile, .{ .read = true });
|
||||
defer result.close();
|
||||
|
||||
var rit = lineIterator(result.reader(), testing.allocator);
|
||||
|
||||
const line1 = rit.next().?;
|
||||
defer testing.allocator.free(line1);
|
||||
try testing.expectEqualStrings("line 2", line1);
|
||||
|
||||
const eof = rit.next();
|
||||
try testing.expect(eof == null);
|
||||
}
|
||||
|
||||
test "dumpInput skip 2 line 'alpha'" {
|
||||
const file = try fs.cwd().openFile("src/test/four-lines.txt", .{ .read = true, .write = false });
|
||||
defer file.close();
|
||||
|
||||
const tempFile = "zig-cache/test.txt";
|
||||
|
||||
const output = try fs.cwd().createFile(tempFile, .{});
|
||||
defer output.close();
|
||||
|
||||
const config = Config{
|
||||
.lines = 2,
|
||||
.file = file,
|
||||
.line = "alpha",
|
||||
};
|
||||
|
||||
try dumpInput(config, file, output, testing.allocator);
|
||||
|
||||
const result = try fs.cwd().openFile(tempFile, .{ .read = true });
|
||||
defer result.close();
|
||||
|
||||
var rit = lineIterator(result.reader(), testing.allocator);
|
||||
|
||||
const line1 = rit.next().?;
|
||||
defer testing.allocator.free(line1);
|
||||
try testing.expectEqualStrings("gamma", line1);
|
||||
|
||||
const eof = rit.next();
|
||||
try testing.expect(eof == null);
|
||||
}
|
||||
|
||||
fn pumpIterator(it: *LineIterator, writer: fs.File.Writer, allocator: mem.Allocator) !void {
|
||||
while (it.next()) |line| {
|
||||
defer allocator.free(line);
|
||||
|
||||
try writer.print("{s}\n", .{ windowsSafe(line) });
|
||||
}
|
||||
}
|
||||
|
||||
test "pumpIterator" {
|
||||
const file = try fs.cwd().openFile("src/test/two-lines.txt", .{ .read = true, .write = false });
|
||||
defer file.close();
|
||||
|
||||
const tempFile = "zig-cache/test.txt";
|
||||
|
||||
const output = try fs.cwd().createFile(tempFile, .{});
|
||||
defer output.close();
|
||||
|
||||
var reader = file.reader();
|
||||
var it = lineIterator(reader, testing.allocator);
|
||||
|
||||
var writer = output.writer();
|
||||
|
||||
try pumpIterator(&it, writer, testing.allocator);
|
||||
|
||||
const result = try fs.cwd().openFile(tempFile, .{ .read = true });
|
||||
defer result.close();
|
||||
|
||||
var rit = lineIterator(result.reader(), testing.allocator);
|
||||
|
||||
const line1 = rit.next().?;
|
||||
defer testing.allocator.free(line1);
|
||||
try testing.expectEqualStrings("line 1", line1);
|
||||
|
||||
const line2 = rit.next().?;
|
||||
defer testing.allocator.free(line2);
|
||||
try testing.expectEqualStrings("line 2", line2);
|
||||
const eof = rit.next();
|
||||
|
||||
try testing.expect(eof == null);
|
||||
}
|
||||
|
||||
const LineIterator = struct {
|
||||
reader: io.BufferedReader(maxLineLength, fs.File.Reader),
|
||||
delimiter: u8,
|
||||
allocator: mem.Allocator,
|
||||
|
||||
const Self = @This();
|
||||
|
||||
/// Caller owns returned memory
|
||||
pub fn next(self: *Self) ?[]u8 {
|
||||
return self.reader.reader().readUntilDelimiterOrEofAlloc(self.allocator, self.delimiter, maxLineLength) catch null;
|
||||
}
|
||||
};
|
||||
|
||||
fn lineIterator(reader: fs.File.Reader, allocator: mem.Allocator) LineIterator {
|
||||
return LineIterator {
|
||||
.reader = io.bufferedReader(reader),
|
||||
.delimiter = '\n',
|
||||
.allocator = allocator
|
||||
};
|
||||
}
|
||||
|
||||
test "lineIterator returns lines in buffer" {
|
||||
const file = try fs.cwd().openFile("src/test/two-lines.txt", .{ .read = true, .write = false });
|
||||
defer file.close();
|
||||
|
||||
var reader = file.reader();
|
||||
var it = lineIterator(reader, testing.allocator);
|
||||
|
||||
const line1 = it.next().?;
|
||||
defer testing.allocator.free(line1);
|
||||
try testing.expectEqualStrings("line 1", line1);
|
||||
|
||||
const line2 = it.next().?;
|
||||
defer testing.allocator.free(line2);
|
||||
try testing.expectEqualStrings("line 2", line2);
|
||||
const eof = it.next();
|
||||
|
||||
try testing.expect(eof == null);
|
||||
}
|
||||
|
||||
fn windowsSafe(line: []const u8) []const u8 {
|
||||
// trim annoying windows-only carriage return character
|
||||
if (os.tag == .windows) {
|
||||
return mem.trimRight(u8, line, "\r");
|
||||
}
|
||||
return line;
|
||||
}
|
||||
|
||||
test "windowsSafe strips carriage return on windows" {
|
||||
const input = "line\n\r";
|
||||
const result = windowsSafe(input);
|
||||
if (os.tag == .windows) {
|
||||
// strips the carriage return if windows
|
||||
try testing.expectEqualSlices(u8, "line\n", result);
|
||||
} else {
|
||||
// doesn't change the line if not windows
|
||||
try testing.expectEqualSlices(u8, input, result);
|
||||
}
|
||||
}
|
106
test.sh
106
test.sh
|
@ -2,27 +2,41 @@
|
|||
|
||||
set -e
|
||||
|
||||
echo "PWD: $PWD"
|
||||
ls -l
|
||||
ls -l target
|
||||
SKIP="./target/debug/skip"
|
||||
DIFF="diff -u --color"
|
||||
|
||||
if test ! -x $SKIP; then
|
||||
echo "File missing: $SKIP - try 'cargo build'"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "> skip a line when reading from stdin"
|
||||
INPUT=$(cat<<EOF
|
||||
INPUT=$(
|
||||
cat <<EOF
|
||||
line 1
|
||||
line 2
|
||||
EOF
|
||||
)
|
||||
echo "line 2" > test.expect
|
||||
echo "$INPUT" | ./skip 1 > test.out
|
||||
diff --brief test.expect test.out
|
||||
echo "line 2" >test.expect
|
||||
echo "$INPUT" | $SKIP 1 >test.out
|
||||
$DIFF test.expect test.out
|
||||
rm test.expect test.out
|
||||
|
||||
echo "> skip a line when reading from a file"
|
||||
cat<<EOF > test.in
|
||||
cat <<EOF >test.in
|
||||
line 1
|
||||
line 2
|
||||
EOF
|
||||
echo "line 2" > test.expect
|
||||
./skip 1 test.in > test.out
|
||||
diff --brief test.expect test.out
|
||||
echo "line 2" >test.expect
|
||||
$SKIP 1 test.in >test.out
|
||||
$DIFF test.expect test.out
|
||||
rm test.expect test.out
|
||||
|
||||
echo "> skip until 2 matching lines seen"
|
||||
cat<<EOF > test.in
|
||||
cat <<EOF >test.in
|
||||
alpha
|
||||
beta
|
||||
alpha
|
||||
|
@ -30,16 +44,17 @@ alpha
|
|||
gamma
|
||||
alpha
|
||||
EOF
|
||||
cat<<EOF > test.expect
|
||||
cat <<EOF >test.expect
|
||||
alpha
|
||||
gamma
|
||||
alpha
|
||||
EOF
|
||||
./skip 2 test.in --line alpha > test.out
|
||||
diff --brief test.expect test.out
|
||||
$SKIP 2 test.in --line alpha >test.out
|
||||
$DIFF test.expect test.out
|
||||
rm test.in test.expect test.out
|
||||
|
||||
echo "> skip lines until 2 tokens seen"
|
||||
cat<<EOF > test.in
|
||||
cat <<EOF >test.in
|
||||
Lorem ipsum dolor sit amet,
|
||||
consectetur adipiscing elit,
|
||||
sed do eiusmod tempor incididunt
|
||||
|
@ -49,15 +64,72 @@ quis nostrud exercitation ullamco
|
|||
laboris nisi ut aliquip ex ea
|
||||
commodo consequat.
|
||||
EOF
|
||||
cat<<EOF > test.expect
|
||||
cat <<EOF >test.expect
|
||||
Ut enim ad minim veniam,
|
||||
quis nostrud exercitation ullamco
|
||||
laboris nisi ut aliquip ex ea
|
||||
commodo consequat.
|
||||
EOF
|
||||
./skip 2 test.in --token dolor > test.out
|
||||
diff --brief test.expect test.out
|
||||
$SKIP 2 test.in --token dolor >test.out
|
||||
$DIFF test.expect test.out
|
||||
rm test.in test.expect test.out
|
||||
|
||||
rm test.in test.out test.expect
|
||||
echo "> handle unknown parameter with simple error message"
|
||||
cat <<EOF >test.expect.err
|
||||
error: unexpected argument '--foo' found
|
||||
|
||||
tip: to pass '--foo' as a value, use '-- --foo'
|
||||
|
||||
Usage: skip [OPTIONS] <LINES> [FILE]
|
||||
|
||||
For more information, try '--help'.
|
||||
EOF
|
||||
cat <<EOF >test.expect
|
||||
EOF
|
||||
touch test.out test.err
|
||||
$SKIP --foo >test.out 2>test.err || true
|
||||
$DIFF test.expect test.out
|
||||
$DIFF test.expect.err test.err
|
||||
rm test.expect test.out
|
||||
rm test.expect.err test.err
|
||||
|
||||
echo "> handle ignore-extra when token is missing"
|
||||
cat <<EOF >test.expect.err
|
||||
error: the following required arguments were not provided:
|
||||
--token <TOKEN>
|
||||
<LINES>
|
||||
|
||||
Usage: skip --ignore-extras --token <TOKEN> <LINES> [FILE]
|
||||
|
||||
For more information, try '--help'.
|
||||
EOF
|
||||
cat <<EOF >test.expect
|
||||
EOF
|
||||
touch test.out test.err
|
||||
$SKIP --ignore-extras >test.out 2>test.err || true
|
||||
$DIFF test.expect test.out
|
||||
$DIFF test.expect.err test.err
|
||||
rm test.expect test.out
|
||||
rm test.expect.err test.err
|
||||
|
||||
echo "> skip lines until 4 tokens seen - ignored extra tokens on same line"
|
||||
cat <<EOF >test.in
|
||||
Lorem ipsum dolor sit amet,
|
||||
consectetur adipiscing elit,
|
||||
sed do eiusmod tempor incididunt
|
||||
ut labore et dolore magna aliqua.
|
||||
Ut enim ad minim veniam,
|
||||
quis nostrud exercitation ullamco
|
||||
laboris nisi ut aliquip ex ea
|
||||
commodo consequat.
|
||||
EOF
|
||||
cat <<EOF >test.expect
|
||||
quis nostrud exercitation ullamco
|
||||
laboris nisi ut aliquip ex ea
|
||||
commodo consequat.
|
||||
EOF
|
||||
$SKIP 4 test.in --token m --ignore-extras >test.out
|
||||
$DIFF test.expect test.out
|
||||
rm test.in test.expect test.out
|
||||
|
||||
echo done
|
||||
|
|
8
tests/lorem.txt
Normal file
8
tests/lorem.txt
Normal file
|
@ -0,0 +1,8 @@
|
|||
Lorem ipsum dolor sit amet,
|
||||
consectetur adipiscing elit,
|
||||
sed do eiusmod tempor incididunt
|
||||
ut labore et dolore magna aliqua.
|
||||
Ut enim ad minim veniam,
|
||||
quis nostrud exercitation ullamco
|
||||
laboris nisi ut aliquip ex ea
|
||||
commodo consequat.
|
7
tests/poem.txt
Normal file
7
tests/poem.txt
Normal file
|
@ -0,0 +1,7 @@
|
|||
If I can stop one heart from breaking,
|
||||
I shall not live in vain;
|
||||
If I can ease one life the aching,
|
||||
Or cool one pain,
|
||||
Or help one fainting robin
|
||||
Unto his nest again,
|
||||
I shall not live in vain.
|
6
zig.mod
6
zig.mod
|
@ -1,6 +0,0 @@
|
|||
id: va9657y7sc352r94jivh9b17lnaffyrou0dlu3x45n5ifm2q
|
||||
name: skip
|
||||
license: MIT
|
||||
description: skip part of a file
|
||||
dev_dependencies:
|
||||
- src: git https://github.com/Hejsil/zig-clap
|
|
@ -1,2 +0,0 @@
|
|||
2
|
||||
git https://github.com/Hejsil/zig-clap commit-802a04a854e65254b0632d9785b2b0f6154686b8
|
Loading…
Reference in a new issue