Compare commits
No commits in common. "main" and "v0.1.0" have entirely different histories.
22 changed files with 471 additions and 813 deletions
|
@ -1,52 +0,0 @@
|
||||||
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
Normal file
6
.gitattributes
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
* 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
Normal file
27
.github/workflows/main.yml
vendored
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
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,3 +1,7 @@
|
||||||
/target
|
zig-cache/
|
||||||
dist/
|
zig-out/
|
||||||
input.txt
|
.zigmod
|
||||||
|
deps.zig
|
||||||
|
/test.in
|
||||||
|
/test.out
|
||||||
|
/test.expect
|
||||||
|
|
|
@ -1,46 +0,0 @@
|
||||||
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
223
Cargo.lock
generated
|
@ -1,223 +0,0 @@
|
||||||
# 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
17
Cargo.toml
|
@ -1,17 +0,0 @@
|
||||||
[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
Normal file
21
LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
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.
|
101
README.md
101
README.md
|
@ -2,29 +2,34 @@
|
||||||
|
|
||||||
Skip part of a file.
|
Skip part of a file.
|
||||||
|
|
||||||
As `head` will show the top of a file up-to a number of line,
|
![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,
|
||||||
so `skip` will do the opposite, and not show the top of the file,
|
so `skip` will do the opposite, and not show the top of the file,
|
||||||
but will show the rest.
|
but will show the rest.
|
||||||
|
|
||||||
Additionally, it can check for whole lines matching,
|
Additionally, it can check for whole lines matching,
|
||||||
or for a token being present on the line.
|
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
|
## Usage
|
||||||
|
|
||||||
### Skip a fixed number of lines
|
### Skip a fixed number of lines
|
||||||
|
|
||||||
This example reads the file from stdin.
|
This example reads the file from stdin.
|
||||||
|
|
||||||
```bash
|
File: `input.txt`
|
||||||
echo "line 1
|
|
||||||
|
```text
|
||||||
|
line 1
|
||||||
line 2
|
line 2
|
||||||
line 3
|
line 3
|
||||||
line 4" > input.txt
|
line 4
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
skip 2 < input.txt
|
skip 2 < input.txt
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -37,18 +42,20 @@ line 4
|
||||||
|
|
||||||
### Skip until a number of matching lines
|
### Skip until a number of matching lines
|
||||||
|
|
||||||
The whole line must match.
|
|
||||||
|
|
||||||
This example reads the named file.
|
This example reads the named file.
|
||||||
|
|
||||||
```bash
|
File: `input.txt`
|
||||||
echo "alpha
|
|
||||||
|
```text
|
||||||
|
alpha
|
||||||
beta
|
beta
|
||||||
alpha
|
alpha
|
||||||
alpha
|
alpha
|
||||||
gamma
|
gamma
|
||||||
alpha" > input.txt
|
alpha
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
skip 2 --line alpha input.txt
|
skip 2 --line alpha input.txt
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -60,22 +67,24 @@ gamma
|
||||||
alpha
|
alpha
|
||||||
```
|
```
|
||||||
|
|
||||||
### Skip lines until a number of tokens are seen
|
### Skip lines until a number of tokens as seen
|
||||||
|
|
||||||
Looks for a string within a line, counting each occurance.
|
|
||||||
|
|
||||||
This example reads the file from stdin.
|
This example reads the file from stdin.
|
||||||
|
|
||||||
```bash
|
File: `input.txt`
|
||||||
echo "Lorem ipsum dolor sit amet,
|
|
||||||
|
```text
|
||||||
|
Lorem ipsum dolor sit amet,
|
||||||
consectetur adipiscing elit,
|
consectetur adipiscing elit,
|
||||||
sed do eiusmod tempor incididunt
|
sed do eiusmod tempor incididunt
|
||||||
ut labore et dolore magna aliqua.
|
ut labore et dolore magna aliqua.
|
||||||
Ut enim ad minim veniam,
|
Ut enim ad minim veniam,
|
||||||
quis nostrud exercitation ullamco
|
quis nostrud exercitation ullamco
|
||||||
laboris nisi ut aliquip ex ea
|
laboris nisi ut aliquip ex ea
|
||||||
commodo consequat." > input.txt
|
commodo consequat.
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
cat input.txt | skip 2 --token dolor
|
cat input.txt | skip 2 --token dolor
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -85,60 +94,8 @@ Will output:
|
||||||
Ut enim ad minim veniam,
|
Ut enim ad minim veniam,
|
||||||
quis nostrud exercitation ullamco
|
quis nostrud exercitation ullamco
|
||||||
laboris nisi ut aliquip ex ea
|
laboris nisi ut aliquip ex ea
|
||||||
commodo consequat.
|
commodo consequat.
|
||||||
```
|
```
|
||||||
|
|
||||||
It matches the first `dolor` on line 1,
|
It matches the first `dolor` on line 1,
|
||||||
and the second on line 4 as part of the word `dolore`.
|
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
Normal file
35
build.zig
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
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
15
justfile
|
@ -1,15 +0,0 @@
|
||||||
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
|
|
|
@ -1,10 +0,0 @@
|
||||||
{
|
|
||||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
|
||||||
"extends": ["config:recommended"],
|
|
||||||
"packageRules": [
|
|
||||||
{
|
|
||||||
"matchManagers": ["cargo"],
|
|
||||||
"rangeStrategy": "replace"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
263
src/lib.rs
263
src/lib.rs
|
@ -1,263 +0,0 @@
|
||||||
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(())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
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
Normal file
321
src/main.zig
Normal file
|
@ -0,0 +1,321 @@
|
||||||
|
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,41 +2,27 @@
|
||||||
|
|
||||||
set -e
|
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"
|
echo "> skip a line when reading from stdin"
|
||||||
INPUT=$(
|
INPUT=$(cat<<EOF
|
||||||
cat <<EOF
|
|
||||||
line 1
|
line 1
|
||||||
line 2
|
line 2
|
||||||
EOF
|
EOF
|
||||||
)
|
)
|
||||||
echo "line 2" >test.expect
|
echo "line 2" > test.expect
|
||||||
echo "$INPUT" | $SKIP 1 >test.out
|
echo "$INPUT" | ./skip 1 > test.out
|
||||||
$DIFF test.expect test.out
|
diff --brief test.expect test.out
|
||||||
rm test.expect test.out
|
|
||||||
|
|
||||||
echo "> skip a line when reading from a file"
|
echo "> skip a line when reading from a file"
|
||||||
cat <<EOF >test.in
|
cat<<EOF > test.in
|
||||||
line 1
|
line 1
|
||||||
line 2
|
line 2
|
||||||
EOF
|
EOF
|
||||||
echo "line 2" >test.expect
|
echo "line 2" > test.expect
|
||||||
$SKIP 1 test.in >test.out
|
./skip 1 test.in > test.out
|
||||||
$DIFF test.expect test.out
|
diff --brief test.expect test.out
|
||||||
rm test.expect test.out
|
|
||||||
|
|
||||||
echo "> skip until 2 matching lines seen"
|
echo "> skip until 2 matching lines seen"
|
||||||
cat <<EOF >test.in
|
cat<<EOF > test.in
|
||||||
alpha
|
alpha
|
||||||
beta
|
beta
|
||||||
alpha
|
alpha
|
||||||
|
@ -44,17 +30,16 @@ alpha
|
||||||
gamma
|
gamma
|
||||||
alpha
|
alpha
|
||||||
EOF
|
EOF
|
||||||
cat <<EOF >test.expect
|
cat<<EOF > test.expect
|
||||||
alpha
|
alpha
|
||||||
gamma
|
gamma
|
||||||
alpha
|
alpha
|
||||||
EOF
|
EOF
|
||||||
$SKIP 2 test.in --line alpha >test.out
|
./skip 2 test.in --line alpha > test.out
|
||||||
$DIFF test.expect test.out
|
diff --brief test.expect test.out
|
||||||
rm test.in test.expect test.out
|
|
||||||
|
|
||||||
echo "> skip lines until 2 tokens seen"
|
echo "> skip lines until 2 tokens seen"
|
||||||
cat <<EOF >test.in
|
cat<<EOF > test.in
|
||||||
Lorem ipsum dolor sit amet,
|
Lorem ipsum dolor sit amet,
|
||||||
consectetur adipiscing elit,
|
consectetur adipiscing elit,
|
||||||
sed do eiusmod tempor incididunt
|
sed do eiusmod tempor incididunt
|
||||||
|
@ -64,72 +49,15 @@ quis nostrud exercitation ullamco
|
||||||
laboris nisi ut aliquip ex ea
|
laboris nisi ut aliquip ex ea
|
||||||
commodo consequat.
|
commodo consequat.
|
||||||
EOF
|
EOF
|
||||||
cat <<EOF >test.expect
|
cat<<EOF > test.expect
|
||||||
Ut enim ad minim veniam,
|
Ut enim ad minim veniam,
|
||||||
quis nostrud exercitation ullamco
|
quis nostrud exercitation ullamco
|
||||||
laboris nisi ut aliquip ex ea
|
laboris nisi ut aliquip ex ea
|
||||||
commodo consequat.
|
commodo consequat.
|
||||||
EOF
|
EOF
|
||||||
$SKIP 2 test.in --token dolor >test.out
|
./skip 2 test.in --token dolor > test.out
|
||||||
$DIFF test.expect test.out
|
diff --brief test.expect test.out
|
||||||
rm test.in test.expect test.out
|
|
||||||
|
|
||||||
echo "> handle unknown parameter with simple error message"
|
rm test.in test.out test.expect
|
||||||
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
|
echo done
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
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.
|
|
|
@ -1,7 +0,0 @@
|
||||||
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
Normal file
6
zig.mod
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
id: va9657y7sc352r94jivh9b17lnaffyrou0dlu3x45n5ifm2q
|
||||||
|
name: skip
|
||||||
|
license: MIT
|
||||||
|
description: skip part of a file
|
||||||
|
dev_dependencies:
|
||||||
|
- src: git https://github.com/Hejsil/zig-clap
|
2
zigmod.lock
Normal file
2
zigmod.lock
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
2
|
||||||
|
git https://github.com/Hejsil/zig-clap commit-802a04a854e65254b0632d9785b2b0f6154686b8
|
Loading…
Reference in a new issue