This commit is contained in:
JIe 2024-11-20 11:44:36 +08:00
commit 1c8e2579b9
32 changed files with 3116 additions and 0 deletions

1
.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
/completions/**/* linguist-generated

15
.github/dependabot.yml vendored Normal file
View File

@ -0,0 +1,15 @@
version: 2
updates:
- package-ecosystem: cargo
directory: "/"
schedule:
interval: weekly
open-pull-requests-limit: 10
reviewers:
- wfxr
labels:
- dependencies
commit-message:
prefix: "chore"
prefix-development: "chore"
include: "scope"

22
.github/issue_template.md vendored Normal file
View File

@ -0,0 +1,22 @@
<!-- ISSUES NOT FOLLOWING THIS TEMPLATE WILL BE CLOSED AND DELETED -->
<!-- Check all that apply [x] -->
## Check list
- [ ] I have read through the [README](https://github.com/wfxr/csview/blob/master/README.md)
- [ ] I have searched through the existing issues
## Environment info
- OS
- [ ] Linux
- [ ] Mac OS X
- [ ] Windows
- [ ] Others:
## Version
<!-- get by running `csview --version` -->
## Problem / Steps to reproduce

29
.github/pull_request_template.md vendored Normal file
View File

@ -0,0 +1,29 @@
<!-- Check all that apply [x] -->
## Check list
- [ ] I have read through the [README](https://github.com/wfxr/csview/blob/master/README.md) (especially F.A.Q section)
- [ ] I have searched through the existing issues or pull requests
- [ ] I have performed a self-review of my code and commented hard-to-understand areas
- [ ] I have made corresponding changes to the documentation (when necessary)
## Description
<!-- Please include a summary of the change(and the related issue if any). Please also include relevant motivation and context when necessary. -->
## Type of change
- [ ] Bug fix
- [ ] New feature
- [ ] Refactor
- [ ] Breaking change
- [ ] Documentation change
- [ ] CICD related improvement
## Test environment
- OS
- [ ] Linux
- [ ] Mac OS X
- [ ] Windows
- [ ] Others:

342
.github/workflows/CICD.yml vendored Normal file
View File

@ -0,0 +1,342 @@
name: CICD
env:
CICD_INTERMEDIATES_DIR: "_cicd-intermediates"
MSRV_FEATURES: "--all-features"
on:
workflow_dispatch:
pull_request:
push:
branches:
- master
tags:
- "*"
jobs:
crate_metadata:
name: Extract crate metadata
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Extract crate information
id: crate_metadata
run: |
cargo metadata --no-deps --format-version 1 | jq -r '"name=" + .packages[0].name' | tee -a $GITHUB_OUTPUT
cargo metadata --no-deps --format-version 1 | jq -r '"version=" + .packages[0].version' | tee -a $GITHUB_OUTPUT
cargo metadata --no-deps --format-version 1 | jq -r '"maintainer=" + .packages[0].authors[0]' | tee -a $GITHUB_OUTPUT
cargo metadata --no-deps --format-version 1 | jq -r '"homepage=" + .packages[0].homepage' | tee -a $GITHUB_OUTPUT
cargo metadata --no-deps --format-version 1 | jq -r '"description=" + .packages[0].description' | tee -a $GITHUB_OUTPUT
cargo metadata --no-deps --format-version 1 | jq -r '"msrv=" + .packages[0].rust_version' | tee -a $GITHUB_OUTPUT
outputs:
name: ${{ steps.crate_metadata.outputs.name }}
version: ${{ steps.crate_metadata.outputs.version }}
maintainer: ${{ steps.crate_metadata.outputs.maintainer }}
homepage: ${{ steps.crate_metadata.outputs.homepage }}
description: ${{ steps.crate_metadata.outputs.description }}
msrv: ${{ steps.crate_metadata.outputs.msrv }}
ensure_cargo_fmt:
name: Ensure 'cargo fmt' has been run
runs-on: ubuntu-latest
steps:
- uses: dtolnay/rust-toolchain@nightly
with:
components: rustfmt
- uses: actions/checkout@v4
- run: cargo fmt -- --check
# min_version:
# name: Minimum supported rust version
# runs-on: ubuntu-latest
# needs: crate_metadata
# steps:
# - name: Checkout source code
# uses: actions/checkout@v4
#
# - name: Install rust toolchain (v${{ needs.crate_metadata.outputs.msrv }})
# uses: dtolnay/rust-toolchain@master
# with:
# toolchain: ${{ needs.crate_metadata.outputs.msrv }}
# components: clippy
# - name: Run clippy (on minimum supported rust version to prevent warnings we can't fix)
# run: cargo clippy --locked --all-targets ${{ env.MSRV_FEATURES }}
# - name: Run tests
# run: cargo test --locked ${{ env.MSRV_FEATURES }}
build:
name: ${{ matrix.job.target }} (${{ matrix.job.os }})
runs-on: ${{ matrix.job.os }}
needs: crate_metadata
strategy:
fail-fast: false
matrix:
job:
- { target: aarch64-unknown-linux-gnu , os: ubuntu-20.04, use-cross: true }
- { target: aarch64-unknown-linux-musl , os: ubuntu-20.04, use-cross: true }
- { target: arm-unknown-linux-gnueabihf , os: ubuntu-20.04, use-cross: true }
- { target: arm-unknown-linux-musleabihf, os: ubuntu-20.04, use-cross: true }
- { target: i686-pc-windows-msvc , os: windows-2019 }
- { target: i686-unknown-linux-gnu , os: ubuntu-20.04, use-cross: true }
- { target: i686-unknown-linux-musl , os: ubuntu-20.04, use-cross: true }
- { target: x86_64-apple-darwin , os: macos-12 }
- { target: aarch64-apple-darwin , os: macos-14 }
# - { target: x86_64-pc-windows-gnu , os: windows-2019 }
- { target: x86_64-pc-windows-msvc , os: windows-2019 }
- { target: x86_64-unknown-linux-gnu , os: ubuntu-20.04, use-cross: true }
- { target: x86_64-unknown-linux-musl , os: ubuntu-20.04, use-cross: true }
env:
BUILD_CMD: cargo
steps:
- name: Checkout source code
uses: actions/checkout@v4
- name: Install prerequisites
shell: bash
run: |
case ${{ matrix.job.target }} in
arm-unknown-linux-*) sudo apt-get -y update ; sudo apt-get -y install gcc-arm-linux-gnueabihf ;;
aarch64-unknown-linux-gnu) sudo apt-get -y update ; sudo apt-get -y install gcc-aarch64-linux-gnu ;;
esac
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@nightly
with:
targets: ${{ matrix.job.target }}
- name: Install cross
if: matrix.job.use-cross
uses: taiki-e/install-action@v2
with:
tool: cross
- name: Overwrite build command env variable
if: matrix.job.use-cross
shell: bash
run: echo "BUILD_CMD=cross" >> $GITHUB_ENV
- name: Show version information (Rust, cargo, GCC)
shell: bash
run: |
gcc --version || true
rustup -V
rustup toolchain list
rustup default
cargo -V
rustc -V
- name: Build
shell: bash
run: $BUILD_CMD build --locked --release --target=${{ matrix.job.target }}
- name: Set binary name & path
id: bin
shell: bash
run: |
# Figure out suffix of binary
EXE_suffix=""
case ${{ matrix.job.target }} in
*-pc-windows-*) EXE_suffix=".exe" ;;
esac;
# Setup paths
BIN_NAME="${{ needs.crate_metadata.outputs.name }}${EXE_suffix}"
BIN_PATH="target/${{ matrix.job.target }}/release/${BIN_NAME}"
# Let subsequent steps know where to find the binary
echo "BIN_PATH=${BIN_PATH}" >> $GITHUB_OUTPUT
echo "BIN_NAME=${BIN_NAME}" >> $GITHUB_OUTPUT
- name: Set testing options
id: test-options
shell: bash
run: |
# test only library unit tests and binary for arm-type targets
unset CARGO_TEST_OPTIONS
unset CARGO_TEST_OPTIONS ; case ${{ matrix.job.target }} in arm-* | aarch64-*) CARGO_TEST_OPTIONS="--bin ${{ needs.crate_metadata.outputs.name }}" ;; esac;
echo "CARGO_TEST_OPTIONS=${CARGO_TEST_OPTIONS}" >> $GITHUB_OUTPUT
- name: Run tests
shell: bash
run: $BUILD_CMD test --locked --target=${{ matrix.job.target }} ${{ steps.test-options.outputs.CARGO_TEST_OPTIONS}}
- name: Test run
shell: bash
run: echo -e 'a,b,c\n1,2,3\n4,5,6\n7,8,9' | $BUILD_CMD run --locked --target=${{ matrix.job.target }}
- name: Strip binary
shell: bash
run: |
if hash strip &>/dev/null; then
# strip binary if possible
strip "${{ steps.bin.outputs.BIN_PATH }}" || true
fi
- name: Create tarball
id: package
shell: bash
run: |
PKG_suffix=".tar.gz" ; case ${{ matrix.job.target }} in *-pc-windows-*) PKG_suffix=".zip" ;; esac;
PKG_BASENAME=${{ needs.crate_metadata.outputs.name }}-v${{ needs.crate_metadata.outputs.version }}-${{ matrix.job.target }}
PKG_NAME=${PKG_BASENAME}${PKG_suffix}
echo "PKG_NAME=${PKG_NAME}" >> $GITHUB_OUTPUT
PKG_STAGING="${{ env.CICD_INTERMEDIATES_DIR }}/package"
ARCHIVE_DIR="${PKG_STAGING}/${PKG_BASENAME}/"
mkdir -p "${ARCHIVE_DIR}"
# Binary
cp "${{ steps.bin.outputs.BIN_PATH }}" "$ARCHIVE_DIR"
# README and LICENSE files
cp "README.md" "LICENSE-MIT" "LICENSE-APACHE" "$ARCHIVE_DIR"
# Autocompletion files
cp -r completions "${ARCHIVE_DIR}"
# base compressed package
pushd "${PKG_STAGING}/" >/dev/null
case ${{ matrix.job.target }} in
*-pc-windows-*) 7z -y a "${PKG_NAME}" "${PKG_BASENAME}"/* | tail -2 ;;
*) tar czf "${PKG_NAME}" "${PKG_BASENAME}"/* ;;
esac;
popd >/dev/null
# Let subsequent steps know where to find the compressed package
echo "PKG_PATH=${PKG_STAGING}/${PKG_NAME}" >> $GITHUB_OUTPUT
- name: Create Debian package
id: debian-package
shell: bash
if: startsWith(matrix.job.os, 'ubuntu')
run: |
COPYRIGHT_YEARS="2020 - "$(date "+%Y")
DPKG_STAGING="${{ env.CICD_INTERMEDIATES_DIR }}/debian-package"
DPKG_DIR="${DPKG_STAGING}/dpkg"
mkdir -p "${DPKG_DIR}"
DPKG_BASENAME=${{ needs.crate_metadata.outputs.name }}
DPKG_CONFLICTS=${{ needs.crate_metadata.outputs.name }}-musl
case ${{ matrix.job.target }} in *-musl*) DPKG_BASENAME=${{ needs.crate_metadata.outputs.name }}-musl ; DPKG_CONFLICTS=${{ needs.crate_metadata.outputs.name }} ;; esac;
DPKG_VERSION=${{ needs.crate_metadata.outputs.version }}
unset DPKG_ARCH
case ${{ matrix.job.target }} in
aarch64-*-linux-*) DPKG_ARCH=arm64 ;;
arm-*-linux-*hf) DPKG_ARCH=armhf ;;
i686-*-linux-*) DPKG_ARCH=i686 ;;
x86_64-*-linux-*) DPKG_ARCH=amd64 ;;
*) DPKG_ARCH=notset ;;
esac;
DPKG_NAME="${DPKG_BASENAME}_${DPKG_VERSION}_${DPKG_ARCH}.deb"
echo "DPKG_NAME=${DPKG_NAME}" >> $GITHUB_OUTPUT
# Binary
install -Dm755 "${{ steps.bin.outputs.BIN_PATH }}" "${DPKG_DIR}/usr/bin/${{ steps.bin.outputs.BIN_NAME }}"
# Man page
# install -Dm644 'doc/${{ needs.crate_metadata.outputs.name }}.1' "${DPKG_DIR}/usr/share/man/man1/${{ needs.crate_metadata.outputs.name }}.1"
# gzip -n --best "${DPKG_DIR}/usr/share/man/man1/${{ needs.crate_metadata.outputs.name }}.1"
# Autocompletion files
install -Dm644 'completions/bash/${{ needs.crate_metadata.outputs.name }}.bash' "${DPKG_DIR}/usr/share/bash-completion/completions/${{ needs.crate_metadata.outputs.name }}"
install -Dm644 'completions/fish/${{ needs.crate_metadata.outputs.name }}.fish' "${DPKG_DIR}/usr/share/fish/vendor_completions.d/${{ needs.crate_metadata.outputs.name }}.fish"
install -Dm644 'completions/zsh/_${{ needs.crate_metadata.outputs.name }}' "${DPKG_DIR}/usr/share/zsh/vendor-completions/_${{ needs.crate_metadata.outputs.name }}"
# README and LICENSE
install -Dm644 "README.md" "${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/README.md"
install -Dm644 "LICENSE-MIT" "${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/LICENSE-MIT"
install -Dm644 "LICENSE-APACHE" "${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/LICENSE-APACHE"
cat > "${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/copyright" <<EOF
Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: ${{ needs.crate_metadata.outputs.name }}
Source: ${{ needs.crate_metadata.outputs.homepage }}
Files: *
Copyright: ${{ needs.crate_metadata.outputs.maintainer }}
Copyright: $COPYRIGHT_YEARS ${{ needs.crate_metadata.outputs.maintainer }}
License: Apache-2.0 or MIT
License: Apache-2.0
On Debian systems, the complete text of the Apache-2.0 can be found in the
file /usr/share/common-licenses/Apache-2.0.
License: MIT
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.
EOF
chmod 644 "${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/copyright"
# control file
mkdir -p "${DPKG_DIR}/DEBIAN"
cat > "${DPKG_DIR}/DEBIAN/control" <<EOF
Package: ${DPKG_BASENAME}
Version: ${DPKG_VERSION}
Section: utils
Priority: optional
Maintainer: ${{ needs.crate_metadata.outputs.maintainer }}
Homepage: ${{ needs.crate_metadata.outputs.homepage }}
Architecture: ${DPKG_ARCH}
Provides: ${{ needs.crate_metadata.outputs.name }}
Conflicts: ${DPKG_CONFLICTS}
Description: ${{ needs.crate_metadata.outputs.description }}
EOF
DPKG_PATH="${DPKG_STAGING}/${DPKG_NAME}"
echo "DPKG_PATH=${DPKG_PATH}" >> $GITHUB_OUTPUT
# build dpkg
fakeroot dpkg-deb --build "${DPKG_DIR}" "${DPKG_PATH}"
- name: "Artifact upload: tarball"
uses: actions/upload-artifact@master
with:
name: ${{ steps.package.outputs.PKG_NAME }}
path: ${{ steps.package.outputs.PKG_PATH }}
- name: "Artifact upload: Debian package"
uses: actions/upload-artifact@master
if: steps.debian-package.outputs.DPKG_NAME
with:
name: ${{ steps.debian-package.outputs.DPKG_NAME }}
path: ${{ steps.debian-package.outputs.DPKG_PATH }}
- name: Check for release
id: is-release
shell: bash
run: |
unset IS_RELEASE ; if [[ $GITHUB_REF =~ ^refs/tags/v[0-9].* ]]; then IS_RELEASE='true' ; fi
echo "IS_RELEASE=${IS_RELEASE}" >> $GITHUB_OUTPUT
- name: Publish archives and packages
uses: softprops/action-gh-release@v1
if: steps.is-release.outputs.IS_RELEASE
with:
files: |
${{ steps.package.outputs.PKG_PATH }}
${{ steps.debian-package.outputs.DPKG_PATH }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

39
.github/workflows/release.yml vendored Normal file
View File

@ -0,0 +1,39 @@
name: Release
on:
push:
tags:
- "v*.*.*"
jobs:
changelog:
name: Generate and publish changelog
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Generate a changelog
uses: orhun/git-cliff-action@v3
id: git-cliff
with:
config: cliff.toml
args: -v --latest --strip header
env:
OUTPUT: CHANGES.md
GITHUB_REPO: ${{ github.repository }}
- name: Polish changelog
shell: bash
run: sed -i '1,2d' CHANGES.md
- name: Upload the changelog
uses: ncipollo/release-action@v1
with:
# draft: true
allowUpdates: true
bodyFile: CHANGES.md

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
/target
/.idea
/flamegraph.svg
/perf.data

43
.pre-commit-config.yaml Normal file
View File

@ -0,0 +1,43 @@
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.0.1
hooks:
- id: trailing-whitespace
- id: check-yaml
- id: check-added-large-files
- id: mixed-line-ending
- id: check-toml
- repo: local
hooks:
- id: cargo-fmt
name: cargo fmt
pass_filenames: false
always_run: true
language: system
entry: cargo fmt
- id: cargo-check
name: cargo check
pass_filenames: false
always_run: true
language: system
entry: cargo check
- id: cargo-clippy
name: cargo clippy
pass_filenames: false
language: system
always_run: true
entry: cargo clippy
args: ["--", "-D", "warnings"]
- id: update-completions
name: update shell completions
pass_filenames: false
language: system
always_run: true
entry: >
sh -c '
touch build.rs &&
SHELL_COMPLETIONS_DIR=completions cargo build &&
git add completions
'

347
CHANGELOG.md Normal file
View File

@ -0,0 +1,347 @@
## [1.3.3](https://github.com/wfxr/csview/compare/v1.3.2..v1.3.3) (2024-07-08)
### 🐛 Bug Fixes
- Workaround error caused by pager env - ([988add5](https://github.com/wfxr/csview/commit/988add57b9de99c61140c68fd868ee8bec79f5a2))
## [1.3.2](https://github.com/wfxr/csview/compare/v1.3.1..v1.3.2) (2024-04-30)
### ⚙️ Miscellaneous Tasks
- Add aarch64-apple-darwin target - ([4481363](https://github.com/wfxr/csview/commit/448136378a3edab2e1669efab63cd81cde20a703))
- Update completions for new option - ([3565cfe](https://github.com/wfxr/csview/commit/3565cfee2dd62c4a22f5752ec261aa0b628e0a4a))
## [1.3.1](https://github.com/wfxr/csview/compare/v1.3.0..v1.3.1) (2024-04-29)
### 🚀 Features
- Only enable pager when stdout is a tty - ([420fd03](https://github.com/wfxr/csview/commit/420fd0310db32d42315fc5dcb2b745a4c4cc6909))
- Allow disabling pager - ([80ae054](https://github.com/wfxr/csview/commit/80ae054283d857814408d186643105b57f7c559d))
### 📚 Documentation
- Remove outdated faq - ([d814a38](https://github.com/wfxr/csview/commit/d814a389eec4a50ee6c0effc19ca601a37ff4395))
### ⚙️ Miscellaneous Tasks
- Remove stale.yml - ([d3ac13b](https://github.com/wfxr/csview/commit/d3ac13b72a6af52cc81ab4482c1db44670fdb307))
## [1.3.0](https://github.com/wfxr/csview/compare/v1.2.4..v1.3.0) (2024-04-15)
### 🚀 Features
- Add pager support ([#198](https://github.com/wfxr/csview/issues/198)) - ([47d3c0a](https://github.com/wfxr/csview/commit/47d3c0a61b6e605ff2ebb0cd4d8d9a60adf758e2))
- Show error when no input file specified - ([1214c96](https://github.com/wfxr/csview/commit/1214c962a6ff2d20eb1bb67fff3c49053406ca5f))
### 🚜 Refactor
- Replace unmaintained `atty` ([#200](https://github.com/wfxr/csview/issues/200)) - ([d60612b](https://github.com/wfxr/csview/commit/d60612b669c41a2f4515f3a3cb067f3be185274e))
### ⚙️ Miscellaneous Tasks
- Update dependencies - ([b9aed24](https://github.com/wfxr/csview/commit/b9aed24bc46e725a6de1c761e3eee2c508ed5338))
- Fix release.toml - ([8b28b89](https://github.com/wfxr/csview/commit/8b28b89824b54fd110f9b73a24d8d839e8db627b))
- Add rust-toolchain.toml & update rustfmt.toml ([#201](https://github.com/wfxr/csview/issues/201)) - ([6fc99d4](https://github.com/wfxr/csview/commit/6fc99d432a9a0670430beef1f74e8da5c126e547))
- Add changelog ([#199](https://github.com/wfxr/csview/issues/199)) - ([a15325d](https://github.com/wfxr/csview/commit/a15325d1ac8d9d90172247b76bb8e3cc734da6d8))
- Add FUNDING.yml - ([0e6045f](https://github.com/wfxr/csview/commit/0e6045fb85d2ca748c7dfd7f4e075fce0641bfe1))
## [1.2.4](https://github.com/wfxr/csview/compare/v1.2.3..v1.2.4) (2024-03-04)
### ⚙️ Miscellaneous Tasks
- Update cicd config ([#191](https://github.com/wfxr/csview/issues/191)) - ([3ea5126](https://github.com/wfxr/csview/commit/3ea512652476364ab2afcbae37d9432ce7695192))
## [1.2.3](https://github.com/wfxr/csview/compare/v1.2.2..v1.2.3) (2024-02-22)
### ⚙️ Miscellaneous Tasks
- Cargo update - ([d903e78](https://github.com/wfxr/csview/commit/d903e78363b850686d2a878ab101fe50f0a6e875))
- Update stale config - ([e39d5a9](https://github.com/wfxr/csview/commit/e39d5a9972d0e742490c068dcc49781ab761e0af))
- Bump up dependencies - ([4e8034f](https://github.com/wfxr/csview/commit/4e8034fda7a2f212898196bf444ff7cf1229ca87))
- Avoid unnecessary ci jobs - ([0cec5ca](https://github.com/wfxr/csview/commit/0cec5cab52cb3e21fc1a3c70830b205302833660))
## [1.2.2](https://github.com/wfxr/csview/compare/v1.2.1..v1.2.2) (2022-10-09)
### 🐛 Bug Fixes
- Fix tests - ([9383041](https://github.com/wfxr/csview/commit/93830413d754f52b1ede1b7b0826f9e489720732))
### 🚜 Refactor
- Adjust error code - ([cf101e9](https://github.com/wfxr/csview/commit/cf101e951a59f482b8271debf7bcef52c6273105))
- Simplify sniff logic - ([9df90a5](https://github.com/wfxr/csview/commit/9df90a547969bcd9b1695ed5c019dffb84d9c916))
- Simplify seq logic - ([14c1c88](https://github.com/wfxr/csview/commit/14c1c88aac405bcd961d846a8949502499a65b12))
### ⚙️ Miscellaneous Tasks
- Upgrade clap to v4 - ([c0380e3](https://github.com/wfxr/csview/commit/c0380e306e272fa2cfd66d6394e2e643e1ac6f18))
- Update stale config - ([4dc8ff1](https://github.com/wfxr/csview/commit/4dc8ff1b0692ba280b8dc8c9cd49f7c3712dfc59))
- Refactor project layout - ([7b0c620](https://github.com/wfxr/csview/commit/7b0c62088e1179b0d160818c24a4baac5172d364))
## [1.2.1](https://github.com/wfxr/csview/compare/v1.2.0..v1.2.1) (2022-09-23)
### 🚜 Refactor
- Serialize enum in lowercase - ([1715587](https://github.com/wfxr/csview/commit/1715587a57fb27bf119fe959c5254ee5da21ab22))
## [1.2.0](https://github.com/wfxr/csview/compare/v1.1.0..v1.2.0) (2022-09-23)
### 🚀 Features
- Add alignment cli options - ([429eb5d](https://github.com/wfxr/csview/commit/429eb5d61cf23d5eee266dd06ee2559af280df6c))
- Add alignment support - ([a01d227](https://github.com/wfxr/csview/commit/a01d2279f1b7b5ffccf04ee0da9a1398a6d30af8))
- Add minimal ascii border style - ([0c5ff8e](https://github.com/wfxr/csview/commit/0c5ff8ecb85b50d3182af99891eac04b5cc55209))
### 📚 Documentation
- Update example - ([af43fc9](https://github.com/wfxr/csview/commit/af43fc9dc9ffee47425e75643aee4df8742927b0))
### ⚙️ Miscellaneous Tasks
- Bump up dependencies - ([d7ddc08](https://github.com/wfxr/csview/commit/d7ddc080fe2cdb5353b8372649e40dce30e5878e))
## [1.1.0](https://github.com/wfxr/csview/compare/v1.0.1..v1.1.0) (2022-07-02)
### 🚀 Features
- Support printing line numbers - ([95b31a5](https://github.com/wfxr/csview/commit/95b31a5700f6a4f460950f0f7baa89559d0c989b))
### 🚜 Refactor
- Fix cargo clippy warnings - ([5a1e6d9](https://github.com/wfxr/csview/commit/5a1e6d9e353313a1ebd86cd01e712458e4c94321))
### 📚 Documentation
- `csview` now in homebrew core repo - ([4fec934](https://github.com/wfxr/csview/commit/4fec9346aba10fc1b2ebfc797d83b8ce0f25a1ca))
### ⚙️ Miscellaneous Tasks
- Bump up dependencies - ([ba43992](https://github.com/wfxr/csview/commit/ba43992ed2e1d8087b2f9b0f9d7a5f315b04a4da))
## [1.0.1](https://github.com/wfxr/csview/compare/v1.0.0..v1.0.1) (2022-02-16)
### 📚 Documentation
- Update readme - ([84a8392](https://github.com/wfxr/csview/commit/84a839265227182467fafc9ef285fb289d0b0eea))
### ⚙️ Miscellaneous Tasks
- Remove unstable features - ([aca9261](https://github.com/wfxr/csview/commit/aca9261dd6d4e1f4245087537750b4969b82308f))
## [1.0.0](https://github.com/wfxr/csview/compare/v0.3.12..v1.0.0) (2022-02-16)
### 🚀 Features
- Change default style - ([2389b4a](https://github.com/wfxr/csview/commit/2389b4a75ce6b91c4188a375ba7bd6df98c3afaa))
- Remove completion sub command - ([d29caba](https://github.com/wfxr/csview/commit/d29cababf42f9b930ef08a3af7b46eb47d78a4e6))
- Add sniff limit option - ([56b4858](https://github.com/wfxr/csview/commit/56b485830165f8a093646b602cde55a37585e0cb))
- Add padding and indent option - ([40d7085](https://github.com/wfxr/csview/commit/40d7085479eefe166b56858f19f62df83f0c34e6))
- Skip title sep when there is no data - ([d53caa5](https://github.com/wfxr/csview/commit/d53caa5675094c33a673208f9ad26193058af781))
- Replace `prettytable-rs` - ([081d965](https://github.com/wfxr/csview/commit/081d9658a038efea94ffc34f85d6903dac1f4ae4))
- Implement table writer - ([9fdb7a6](https://github.com/wfxr/csview/commit/9fdb7a6903f0c6c85caea8990ad618893c8ca170))
### 🚜 Refactor
- Rename & clean derive traits - ([d77a5b0](https://github.com/wfxr/csview/commit/d77a5b0fe5b10ebd9900c392585b314b0f1d09b2))
- Remove cell - ([46bd2c4](https://github.com/wfxr/csview/commit/46bd2c414778b44155c20e778cba88c5784f978d))
- Lint - ([cf5f340](https://github.com/wfxr/csview/commit/cf5f3406175fefc8a255af2b3fee6f658d146e1b))
- Rename - ([573e297](https://github.com/wfxr/csview/commit/573e297c52695d12a61a9faec2e4bac946cadceb))
- Remove redundant enums - ([863c97e](https://github.com/wfxr/csview/commit/863c97e74cd4f1f1b08711fe77e63b39b2d0a37c))
- Rename - ([d6b336a](https://github.com/wfxr/csview/commit/d6b336a4c910e26563c4877fe809110c47cbc226))
- Tweak cli options - ([b574e79](https://github.com/wfxr/csview/commit/b574e79b7b7d055ccd515a0ffa19ce4515a8e80c))
### 📚 Documentation
- Update default style - ([bab68ce](https://github.com/wfxr/csview/commit/bab68ce17c106ffea523b522116a100132dad33a))
- Fix time unit - ([b21642c](https://github.com/wfxr/csview/commit/b21642c5c550af87311bfc2e7ff78049d979f933))
- Update - ([4d4461e](https://github.com/wfxr/csview/commit/4d4461e3ffec65dd2be0d6f1c87f6bd1b2c43fad))
- Update - ([a83ad55](https://github.com/wfxr/csview/commit/a83ad555539bbe6a0e0768c01f9d0600f3bdc0cb))
### ⚡ Performance
- Add buffer on writer - ([86c6101](https://github.com/wfxr/csview/commit/86c610119d506b0a09453c5f3b3a3a1223ae5cca))
- Remove unnecessary buffer - ([ffc59ca](https://github.com/wfxr/csview/commit/ffc59ca90e2aa2c1fdcdc1c20e9114e693dc5c71))
### 🧪 Testing
- Add no padding test - ([ca15128](https://github.com/wfxr/csview/commit/ca15128b1b2a0ec60e3f1e043946f87cbd96bb36))
### ⚙️ Miscellaneous Tasks
- Bump up version - ([8cd76c7](https://github.com/wfxr/csview/commit/8cd76c73a75d0db1db7687d131d54d398f5b2967))
- NextLineHelp - ([cb795c9](https://github.com/wfxr/csview/commit/cb795c95a8ba941e32127798ab515059b2f6d33b))
- Switched to nightly rust - ([abb2300](https://github.com/wfxr/csview/commit/abb23008218c103cb6afba38c1381181b2d79908))
## [0.3.11](https://github.com/wfxr/csview/compare/v0.3.10..v0.3.11) (2022-01-01)
### 🚜 Refactor
- Misc - ([35bca57](https://github.com/wfxr/csview/commit/35bca57f605fa825d56d965eff1c7ea200d5c570))
### ⚙️ Miscellaneous Tasks
- Update dependabot config - ([2d2cc6d](https://github.com/wfxr/csview/commit/2d2cc6d1ab09ac5349de0e426a6fc60ee267826c))
- Clap v3 - ([569552b](https://github.com/wfxr/csview/commit/569552b57c5c5d922885aeeec515c67156b6c5ad))
## [0.3.10](https://github.com/wfxr/csview/compare/v0.3.9..v0.3.10) (2021-12-26)
### 🚀 Features
- Upgrade cli - ([2b40db4](https://github.com/wfxr/csview/commit/2b40db482fbe9b86fff74618590154941dd21939))
### 📚 Documentation
- Update - ([7934cf7](https://github.com/wfxr/csview/commit/7934cf758b268c7dde0caa1b91d8b503414b18ff))
### ⚙️ Miscellaneous Tasks
- Add rustfmt.toml - ([a0b9025](https://github.com/wfxr/csview/commit/a0b9025e930dbd947363e0149c0ffaa3332280e4))
### Typo
- Misc - ([ce696ba](https://github.com/wfxr/csview/commit/ce696ba7ff2b89680b7bc4b9bb5bb145a86872f5))
## [0.3.9](https://github.com/wfxr/csview/compare/v0.3.8..v0.3.9) (2021-11-23)
### 🚜 Refactor
- Clippy lint - ([f36edb1](https://github.com/wfxr/csview/commit/f36edb1bdd49c6916d76d3f3687e4cde876c58e6))
### 📚 Documentation
- Add pkgsrc installation. - ([9888062](https://github.com/wfxr/csview/commit/9888062d1b58de4f5a11ed05970c809dcab012a7))
### ⚙️ Miscellaneous Tasks
- Update license - ([a1de726](https://github.com/wfxr/csview/commit/a1de726f18fd22de1c4d4111a56aee2328262b75))
- Update ci config - ([33b2b94](https://github.com/wfxr/csview/commit/33b2b946833952eeda82fa04d6d92888f4434843))
- Update linguist - ([a82edc9](https://github.com/wfxr/csview/commit/a82edc94c48ceab598bbf510481544763e0990e1))
- Upgrade pre-commit version - ([26fe3d8](https://github.com/wfxr/csview/commit/26fe3d86b9bdf4e1497f3deb28d21677402cae03))
- Upgrade to 2021 edition - ([0035c2e](https://github.com/wfxr/csview/commit/0035c2e6c636fc574e589dc5b00bfb6b0e5db3e0))
## [0.3.8](https://github.com/wfxr/csview/compare/v0.3.7..v0.3.8) (2021-04-05)
### 🐛 Bug Fixes
- Fix output when table is empty ([#8](https://github.com/wfxr/csview/issues/8)) - ([2fbe3fd](https://github.com/wfxr/csview/commit/2fbe3fd051babe2531bae92f6cca6a495841676e))
## [0.3.7](https://github.com/wfxr/csview/compare/v0.3.6..v0.3.7) (2021-03-19)
### 🚜 Refactor
- Improve error handling - ([67f7b44](https://github.com/wfxr/csview/commit/67f7b44dfa139ba56e24cf0b48001487fddfb0a9))
- Error handling - ([b6c38ec](https://github.com/wfxr/csview/commit/b6c38ecd7f2060f5b906282324725f400ec8e26d))
### ⚙️ Miscellaneous Tasks
- Add dependabot - ([f5e5920](https://github.com/wfxr/csview/commit/f5e5920677a5f4fc8317f0fc77645de681c68ea1))
## [0.3.6](https://github.com/wfxr/csview/compare/v0.3.5..v0.3.6) (2021-02-07)
### 🚜 Refactor
- Minor refactor & bump up dependencies - ([6edd84e](https://github.com/wfxr/csview/commit/6edd84e90056949916797b25f5aae56ddac8ed37))
### 📚 Documentation
- Add install description for windows - ([06b3006](https://github.com/wfxr/csview/commit/06b3006971066b77a516b8c2e90ef4c306f6a781))
### ⚙️ Miscellaneous Tasks
- Add .gitattributes - ([ca098b0](https://github.com/wfxr/csview/commit/ca098b0e5f9de1dd65993916220e683e9f131a13))
- Update clippy hook - ([2aafe79](https://github.com/wfxr/csview/commit/2aafe7941bdc50e33a0d5d5d1427f1828e58b77b))
## [0.3.5](https://github.com/wfxr/csview/compare/v0.3.4..v0.3.5) (2020-09-23)
### 🚀 Features
- Add grid style - ([3b758a9](https://github.com/wfxr/csview/commit/3b758a9a406bede7e921dcb7c716ff3e2a5ac2b2))
### 📚 Documentation
- Add macOS installation - ([985d643](https://github.com/wfxr/csview/commit/985d6439c9d79ac648e9930ee87895193d0550b7))
- Fix table indent in README - ([5356a4d](https://github.com/wfxr/csview/commit/5356a4d462c4ffb85f3bb162bef192c5a0787d40))
- Add arch linux installation - ([e0c178b](https://github.com/wfxr/csview/commit/e0c178b8f39b46b9a8dfdd9ba35e3f64206ac4ae))
## [0.3.4](https://github.com/wfxr/csview/compare/v0.3.3..v0.3.4) (2020-09-19)
### 🚜 Refactor
- Use exit code defined in sysexits.h - ([c52153e](https://github.com/wfxr/csview/commit/c52153e9f1c187953c663ee0e90c12bcc0d1c38c))
### 📚 Documentation
- Misc - ([177f555](https://github.com/wfxr/csview/commit/177f555f17aff140319388b3ba57308a2ee47499))
- Add installation section - ([320fd43](https://github.com/wfxr/csview/commit/320fd435f97206dffafe2238af99689e1e1b16df))
### ⚙️ Miscellaneous Tasks
- Remove duplicated hooks - ([a0e3ca3](https://github.com/wfxr/csview/commit/a0e3ca3e85c0cab427eba54fcac7eae5a068cfc4))
- Words - ([5691214](https://github.com/wfxr/csview/commit/5691214a66abf188e6c77f6a568dcddcc4947477))
- Add probot-stale config - ([2f036c2](https://github.com/wfxr/csview/commit/2f036c25082e1e2eab1fc3577b3730e858302210))
- Add issue/pr templates - ([4b1ba13](https://github.com/wfxr/csview/commit/4b1ba136ad9d5a537987d0cdec88eee64c9a8a6b))
## [0.3.3](https://github.com/wfxr/csview/compare/v0.3.2..v0.3.3) (2020-09-19)
### 🚜 Refactor
- Improve error handling ([#2](https://github.com/wfxr/csview/issues/2)) - ([511ac52](https://github.com/wfxr/csview/commit/511ac52313df9aa409a9f48142fb108d72ebb26a))
### 📚 Documentation
- Add credits section - ([a037d45](https://github.com/wfxr/csview/commit/a037d455327d41d25fd15e3a27fc989d1560f024))
- Add features section - ([7cf4aa6](https://github.com/wfxr/csview/commit/7cf4aa6ae126bbc117babd04f358beddd21fb4fc))
- Add faq section - ([841bd55](https://github.com/wfxr/csview/commit/841bd55c63a5dc7e826ba4408192afb4f89c3710))
### ⚙️ Miscellaneous Tasks
- Add about message - ([a2b2f9d](https://github.com/wfxr/csview/commit/a2b2f9dc50705c9fa19862e2b9501d6a2578240f))
### Lint
- Cargo clippy - ([b1cb104](https://github.com/wfxr/csview/commit/b1cb104788062bc0e88c105eee5cb9275bbc8362))
## [0.3.2](https://github.com/wfxr/csview/compare/v0.3.1..v0.3.2) (2020-09-18)
### 🚀 Features
- Add markdown style - ([9fb038e](https://github.com/wfxr/csview/commit/9fb038e09e5501a7d5e51a704b99467f532dc18e))
### 📚 Documentation
- Fix crates badge - ([a24e2be](https://github.com/wfxr/csview/commit/a24e2be7391029a1947c1e06565bd2f46341526b))
### ⚙️ Miscellaneous Tasks
- Fix a typo ([#1](https://github.com/wfxr/csview/issues/1)) - ([a5e2e39](https://github.com/wfxr/csview/commit/a5e2e3998472f70b523bbc4886f932fea960b7a9))
## [0.3.1](https://github.com/wfxr/csview/compare/v0.3.0..v0.3.1) (2020-09-17)
### 📚 Documentation
- Add more descriptions - ([113609f](https://github.com/wfxr/csview/commit/113609f0590190dffcad799fa9862a1b0b9012e0))
- Update readme - ([74c07f4](https://github.com/wfxr/csview/commit/74c07f419435ac7dbc5f87422695db787cd42c7d))
- Update readme - ([906c747](https://github.com/wfxr/csview/commit/906c7474df7bd0860d4d72f35a1714a2fb76c46e))
- Update help messages - ([1d48b23](https://github.com/wfxr/csview/commit/1d48b23eb18d52d2efe5bfb3236682a859a7ad1c))
## [0.3.0] - 2020-09-17
### 🚀 Features
- Show possible values for shells - ([c2bbcbc](https://github.com/wfxr/csview/commit/c2bbcbca944d3fc27bd76314ae7ffed72c24d504))
- Support border style - ([7be10ae](https://github.com/wfxr/csview/commit/7be10ae0148170e7ac6c3eca0797f9ae2bf83906))
- Support tsv - ([d53fb02](https://github.com/wfxr/csview/commit/d53fb02bffab217b5cd1c55ff14defe694c73ded))
- Add delimeter support - ([cc9a1b0](https://github.com/wfxr/csview/commit/cc9a1b075e5942e1c36f5b62077595263705c5fb))
- Use prettytable to print - ([751980c](https://github.com/wfxr/csview/commit/751980cab7be9d9e4c4079835bb5b9a1b7ca30e7))
### 🚜 Refactor
- Remove update command - ([3002149](https://github.com/wfxr/csview/commit/300214906649498b9713ebaa69cea7128ad43220))
### ⚙️ Miscellaneous Tasks
- Add pre-commit hooks - ([cbc60d4](https://github.com/wfxr/csview/commit/cbc60d42287aef1e225961c33ff9a97f3a1c0a6d))
- Ci & cd - ([985bd1e](https://github.com/wfxr/csview/commit/985bd1e2b9255e9a58709a376b7ce1b99f4f1fca))
<!-- generated by git-cliff -->

533
Cargo.lock generated Normal file
View File

@ -0,0 +1,533 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "anstream"
version = "0.6.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1"
[[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 0.52.0",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7"
dependencies = [
"anstyle",
"windows-sys 0.52.0",
]
[[package]]
name = "anyhow"
version = "1.0.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6"
[[package]]
name = "bitflags"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
[[package]]
name = "cc"
version = "1.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17f6e324229dc011159fcc089755d1e2e216a90d43a7dea6853ca740b84f35e7"
[[package]]
name = "clap"
version = "4.5.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e5a21b8495e732f1b3c364c9949b201ca7bae518c502c80256c96ad79eaf6ac"
dependencies = [
"clap_builder",
"clap_derive",
]
[[package]]
name = "clap_builder"
version = "4.5.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8cf2dd12af7a047ad9d6da2b6b249759a22a7abc0f474c1dae1777afa4b21a73"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
"strsim",
"terminal_size",
]
[[package]]
name = "clap_complete"
version = "4.5.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "205d5ef6d485fa47606b98b0ddc4ead26eb850aaa86abfb562a94fb3280ecba0"
dependencies = [
"clap",
]
[[package]]
name = "clap_derive"
version = "4.5.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0"
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 = "csv"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac574ff4d437a7b5ad237ef331c17ccca63c46479e5b5453eb8e10bb99a759fe"
dependencies = [
"csv-core",
"itoa",
"ryu",
"serde",
]
[[package]]
name = "csv-core"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70"
dependencies = [
"memchr",
]
[[package]]
name = "csview"
version = "1.3.3"
dependencies = [
"anyhow",
"clap",
"clap_complete",
"csv",
"exitcode",
"itertools",
"pager",
"unicode-truncate",
"unicode-width",
]
[[package]]
name = "either"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2"
[[package]]
name = "errno"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1"
dependencies = [
"errno-dragonfly",
"libc",
"winapi",
]
[[package]]
name = "errno"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
dependencies = [
"libc",
"windows-sys 0.52.0",
]
[[package]]
name = "errno-dragonfly"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
dependencies = [
"cc",
"libc",
]
[[package]]
name = "exitcode"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de853764b47027c2e862a995c34978ffa63c1501f2e15f987ba11bd4f9bba193"
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "itertools"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
[[package]]
name = "libc"
version = "0.2.153"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
[[package]]
name = "linux-raw-sys"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
[[package]]
name = "memchr"
version = "2.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d"
[[package]]
name = "pager"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2599211a5c97fbbb1061d3dc751fa15f404927e4846e07c643287d6d1f462880"
dependencies = [
"errno 0.2.8",
"libc",
]
[[package]]
name = "proc-macro2"
version = "1.0.80"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a56dea16b0a29e94408b9aa5e2940a4eedbd128a1ba20e8f7ae60fd3d465af0e"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rustix"
version = "0.38.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89"
dependencies = [
"bitflags",
"errno 0.3.8",
"libc",
"linux-raw-sys",
"windows-sys 0.52.0",
]
[[package]]
name = "ryu"
version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1"
[[package]]
name = "serde"
version = "1.0.197"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.197"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "syn"
version = "2.0.59"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a6531ffc7b071655e4ce2e04bd464c4830bb585a61cabb96cf808f05172615a"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "terminal_size"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7"
dependencies = [
"rustix",
"windows-sys 0.48.0",
]
[[package]]
name = "unicode-ident"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "unicode-segmentation"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202"
[[package]]
name = "unicode-truncate"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf"
dependencies = [
"itertools",
"unicode-segmentation",
"unicode-width",
]
[[package]]
name = "unicode-width"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d"
[[package]]
name = "utf8parse"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
"windows-targets 0.48.5",
]
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets 0.52.5",
]
[[package]]
name = "windows-targets"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
dependencies = [
"windows_aarch64_gnullvm 0.48.5",
"windows_aarch64_msvc 0.48.5",
"windows_i686_gnu 0.48.5",
"windows_i686_msvc 0.48.5",
"windows_x86_64_gnu 0.48.5",
"windows_x86_64_gnullvm 0.48.5",
"windows_x86_64_msvc 0.48.5",
]
[[package]]
name = "windows-targets"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb"
dependencies = [
"windows_aarch64_gnullvm 0.52.5",
"windows_aarch64_msvc 0.52.5",
"windows_i686_gnu 0.52.5",
"windows_i686_gnullvm",
"windows_i686_msvc 0.52.5",
"windows_x86_64_gnu 0.52.5",
"windows_x86_64_gnullvm 0.52.5",
"windows_x86_64_msvc 0.52.5",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6"
[[package]]
name = "windows_i686_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
[[package]]
name = "windows_i686_gnu"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9"
[[package]]
name = "windows_i686_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
[[package]]
name = "windows_i686_msvc"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"

37
Cargo.toml Normal file
View File

@ -0,0 +1,37 @@
[package]
name = "csview"
version = "1.3.3"
authors = ["Wenxuan Zhang <wenxuangm@gmail.com>"]
description = "A high performance csv viewer with cjk/emoji support."
categories = ["command-line-utilities"]
homepage = "https://github.com/wfxr/csview"
keywords = ["csv", "pager", "viewer", "tool"]
readme = "README.md"
license = "MIT OR Apache-2.0"
exclude = ["/completions"]
repository = "https://github.com/wfxr/csview"
edition = "2021"
build = "build.rs"
[features]
default = ["pager"]
pager = ["dep:pager"]
[dependencies]
csv = "1.3"
clap = { version = "4", features = ["wrap_help", "derive"] }
exitcode = "1.1"
anyhow = "1.0"
unicode-width = "0"
unicode-truncate = "1.1"
itertools = "0.13"
[target.'cfg(target_family = "unix")'.dependencies]
pager = { version = "0.16", optional = true }
[build-dependencies]
clap = { version = "4", features = ["wrap_help", "derive"] }
clap_complete = "4"
[profile.release]
lto = true
codegen-units = 1

1
FUNDING.yml Normal file
View File

@ -0,0 +1 @@
ko_fi: wfxr

201
LICENSE-APACHE Normal file
View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

19
LICENSE-MIT Normal file
View File

@ -0,0 +1,19 @@
Copyright (c) 2020 Wenxuan Zhang (https://wfxr.mit-license.org/2020).
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.

190
README.md Normal file
View File

@ -0,0 +1,190 @@
<h1 align="center">📠 csview</h1>
<p align="center">
<em>A high performance csv viewer with cjk/emoji support.</em>
</p>
<p align="center">
<a href="https://github.com/wfxr/csview/actions?query=workflow%3ACICD">
<img src="https://github.com/wfxr/csview/workflows/CICD/badge.svg" alt="CICD"/>
</a>
<img src="https://img.shields.io/crates/l/csview.svg" alt="License"/>
<a href="https://crates.io/crates/csview">
<img src="https://img.shields.io/crates/v/csview.svg?colorB=319e8c" alt="Version">
</a>
<a href="https://github.com/wfxr/csview/releases">
<img src="https://img.shields.io/badge/platform-%20Linux%20|%20OSX%20|%20Win%20|%20ARM-orange.svg" alt="Platform"/>
</a>
</p>
<img src="https://raw.githubusercontent.com/wfxr/i/master/csview-screenshot.png" />
### Features
* Small and *fast* (see [benchmarks](#benchmark) below).
* Memory efficient.
* Correctly align [CJK](https://en.wikipedia.org/wiki/CJK_characters) and emoji characters.
* Support `tsv` and custom delimiters.
* Support different styles, including markdown table.
### Usage
```
$ cat example.csv
Year,Make,Model,Description,Price
1997,Ford,E350,"ac, abs, moon",3000.00
1999,Chevy,"Venture ""Extended Edition""","",4900.00
1999,Chevy,"Venture ""Extended Edition, Large""",,5000.00
1996,Jeep,Grand Cherokee,"MUST SELL! air, moon roof",4799.00
$ csview example.csv
┌──────┬───────┬───────────────────────────────────┬───────────────────────────┬─────────┐
│ Year │ Make │ Model │ Description │ Price │
├──────┼───────┼───────────────────────────────────┼───────────────────────────┼─────────┤
│ 1997 │ Ford │ E350 │ ac, abs, moon │ 3000.00 │
│ 1999 │ Chevy │ Venture "Extended Edition" │ │ 4900.00 │
│ 1999 │ Chevy │ Venture "Extended Edition, Large" │ │ 5000.00 │
│ 1996 │ Jeep │ Grand Cherokee │ MUST SELL! air, moon roof │ 4799.00 │
└──────┴───────┴───────────────────────────────────┴───────────────────────────┴─────────┘
$ head /etc/passwd | csview -H -d:
┌────────────────────────┬───┬───────┬───────┬────────────────────────────┬─────────────────┐
│ root │ x │ 0 │ 0 │ │ /root │
│ bin │ x │ 1 │ 1 │ │ / │
│ daemon │ x │ 2 │ 2 │ │ / │
│ mail │ x │ 8 │ 12 │ │ /var/spool/mail │
│ ftp │ x │ 14 │ 11 │ │ /srv/ftp │
│ http │ x │ 33 │ 33 │ │ /srv/http │
│ nobody │ x │ 65534 │ 65534 │ Nobody │ / │
│ dbus │ x │ 81 │ 81 │ System Message Bus │ / │
│ systemd-journal-remote │ x │ 981 │ 981 │ systemd Journal Remote │ / │
│ systemd-network │ x │ 980 │ 980 │ systemd Network Management │ / │
└────────────────────────┴───┴───────┴───────┴────────────────────────────┴─────────────────┘
```
Run `csview --help` to view detailed usage.
### Installation
#### On Arch Linux
`csview` is available in the Arch User Repository. To install it from [AUR](https://aur.archlinux.org/packages/csview):
```
yay -S csview
```
#### On macOS
You can install `csview` with Homebrew:
```
brew install csview
```
#### On NetBSD
`csview` is available from the main pkgsrc Repositories. To install simply run
```
pkgin install csview
```
or, if you prefer to build from source using [pkgsrc](https://pkgsrc.se/textproc/csview) on any of the supported platforms:
```
cd /usr/pkgsrc/textproc/csview
make install
```
#### On Windows
You can install `csview` with [Scoop](https://scoop.sh/):
```
scoop install csview
```
#### From binaries
Pre-built versions of `csview` for various architectures are available at [Github release page](https://github.com/wfxr/csview/releases).
*Note that you can try the `musl` version (which is statically-linked) if runs into dependency related errors.*
#### From source
`csview` is also published on [crates.io](https://crates.io). If you have latest Rust toolchains installed you can use `cargo` to install it from source:
```
cargo install --locked csview
```
If you want the latest version, clone this repository and run `cargo build --release`.
### Benchmark
- [small.csv](https://gist.github.com/wfxr/567e890d4db508b3c7630a96b703a57e#file-action-csv) (10 rows, 4 cols, 695 bytes):
| Tool | Command | Mean Time | Min Time | Memory |
|:----------------------------------------------------------------------------------------:|---------------------------|----------:|----------:|----------:|
| [xsv](https://github.com/BurntSushi/xsv/tree/0.13.0) | `xsv table small.csv` | 2.0ms | 1.8ms | 3.9mb |
| [csview](https://github.com/wfxr/csview/tree/90ff90e26c3e4c4c37818d717555b3e8f90d27e3) | `csview small.csv` | **0.3ms** | **0.1ms** | **2.4mb** |
| [column](https://github.com/util-linux/util-linux/blob/stable/v2.37/text-utils/column.c) | `column -s, -t small.csv` | 1.3ms | 1.1ms | **2.4mb** |
| [csvlook](https://github.com/wireservice/csvkit/tree/1.0.6) | `csvlook small.csv` | 148.1ms | 142.4ms | 27.3mb |
- [medium.csv](https://gist.github.com/wfxr/567e890d4db508b3c7630a96b703a57e#file-sample-csv) (10,000 rows, 10 cols, 624K bytes):
| Tool | Command | Mean Time | Min Time | Memory |
|:----------------------------------------------------------------------------------------:|---------------------------|-----------:|-----------:|----------:|
| [xsv](https://github.com/BurntSushi/xsv/tree/0.13.0) | `xsv table medium.csv` | 0.031s | 0.029s | 4.4mb |
| [csview](https://github.com/wfxr/csview/tree/90ff90e26c3e4c4c37818d717555b3e8f90d27e3) | `csview medium.csv` | **0.017s** | **0.016s** | **2.8mb** |
| [column](https://github.com/util-linux/util-linux/blob/stable/v2.37/text-utils/column.c) | `column -s, -t small.csv` | 0.052s | 0.050s | 9.9mb |
| [csvlook](https://github.com/wireservice/csvkit/tree/1.0.6) | `csvlook medium.csv` | 2.664s | 2.617s | 46.8mb |
- `large.csv` (1,000,000 rows, 10 cols, 61M bytes, generated by concatenating [medium.csv](https://gist.github.com/wfxr/567e890d4db508b3c7630a96b703a57e#file-sample-csv) 100 times):
| Tool | Command | Mean Time | Min Time | Memory |
|:----------------------------------------------------------------------------------------:|---------------------------|-----------:|-----------:|----------:|
| [xsv](https://github.com/BurntSushi/xsv/tree/0.13.0) | `xsv table large.csv` | 2.912s | 2.820s | 4.4mb |
| [csview](https://github.com/wfxr/csview/tree/90ff90e26c3e4c4c37818d717555b3e8f90d27e3) | `csview large.csv` | **1.686s** | **1.665s** | **2.8mb** |
| [column](https://github.com/util-linux/util-linux/blob/stable/v2.37/text-utils/column.c) | `column -s, -t small.csv` | 5.777s | 5.759s | 767.6mb |
| [csvlook](https://github.com/wireservice/csvkit/tree/1.0.6) | `csvlook large.csv` | 20.665s | 20.549s | 1105.7mb |
### F.A.Q.
---
#### We already have [xsv](https://github.com/BurntSushi/xsv), why not contribute to it but build a new tool?
`xsv` is great. But it's aimed for analyzing and manipulating csv data.
`csview` is designed for formatting and viewing. See also: [xsv/issues/156](https://github.com/BurntSushi/xsv/issues/156)
---
#### I encountered UTF-8 related errors, how to solve it?
The file may use a non-UTF8 encoding. You can check the file encoding using `file` command:
```
$ file -i a.csv
a.csv: application/csv; charset=iso-8859-1
```
And then convert it to `utf8`:
```
$ iconv -f iso-8859-1 -t UTF8//TRANSLIT a.csv -o b.csv
$ csview b.csv
```
Or do it in place:
```
$ iconv -f iso-8859-1 -t UTF8//TRANSLIT a.csv | csview
```
### Credits
* [csv-rust](https://github.com/BurntSushi/rust-csv)
* [prettytable-rs](https://github.com/phsym/prettytable-rs)
* [structopt](https://github.com/TeXitoi/structopt)
### License
`csview` is distributed under the terms of both the MIT License and the Apache License 2.0.
See the [LICENSE-APACHE](LICENSE-APACHE) and [LICENSE-MIT](LICENSE-MIT) files for license details.

20
build.rs Normal file
View File

@ -0,0 +1,20 @@
use clap::CommandFactory;
use clap_complete::Shell;
use std::{fs, path::Path};
include!("src/cli.rs");
fn main() -> Result<(), Box<dyn std::error::Error>> {
let outdir = std::env::var_os("SHELL_COMPLETIONS_DIR")
.or_else(|| std::env::var_os("OUT_DIR"))
.expect("OUT_DIR not found");
let outdir_path = Path::new(&outdir);
let app = &mut App::command();
for shell in Shell::value_variants() {
let dir = outdir_path.join(shell.to_string());
fs::create_dir_all(&dir)?;
clap_complete::generate_to(*shell, app, app.get_name().to_string(), &dir)?;
}
Ok(())
}

107
cliff.toml Normal file
View File

@ -0,0 +1,107 @@
# git-cliff ~ configuration file
# https://git-cliff.org/docs/configuration
#
# Lines starting with "#" are comments.
# Configuration options are organized into tables and keys.
# See documentation for more information on available options.
[changelog]
# changelog header
header = """
"""
# template for the changelog body
# https://keats.github.io/tera/docs/#introduction
body = """
{%- macro remote_url() -%}
https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }}
{%- endmacro -%}
{% macro print_commit(commit) -%}
- {% if commit.scope %}*({{ commit.scope }})* {% endif %}\
{% if commit.breaking %}[**breaking**] {% endif %}\
{{ commit.message | upper_first }} - \
([{{ commit.id | truncate(length=7, end="") }}]({{ self::remote_url() }}/commit/{{ commit.id }}))\
{% endmacro -%}
{% if version %}\
{% if previous.version %}\
## [{{ version | trim_start_matches(pat="v") }}]\
({{ self::remote_url() }}/compare/{{ previous.version }}..{{ version }}) ({{ timestamp | date(format="%Y-%m-%d") }})
{% else %}\
## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
{% endif %}\
{% else %}\
## [unreleased]
{% endif %}\
{% for group, commits in commits | group_by(attribute="group") %}
### {{ group | striptags | trim | upper_first }}
{% for commit in commits
| filter(attribute="scope")
| sort(attribute="scope") %}
{{ self::print_commit(commit=commit) }}
{%- endfor -%}
{% raw %}\n{% endraw %}\
{%- for commit in commits %}
{%- if not commit.scope -%}
{{ self::print_commit(commit=commit) }}
{% endif -%}
{% endfor -%}
{% endfor %}\n
"""
# template for the changelog footer
footer = """
<!-- generated by git-cliff -->
"""
# remove the leading and trailing whitespace from the templates
trim = true
# postprocessors
postprocessors = [
{ pattern = '<REPO>', replace = "https://github.com/wfxr/csview" }, # replace repository URL
]
[git]
# parse the commits based on https://www.conventionalcommits.org
conventional_commits = true
# filter out the commits that are not conventional
filter_unconventional = true
# process each line of a commit as an individual commit
split_commits = false
# regex for preprocessing the commit messages
commit_preprocessors = [
{ pattern = ' (#[0-9]+)', replace = '(${1})' },
{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](<REPO>/issues/${2}))" },
]
# regex for parsing and grouping commits
commit_parsers = [
{ message = "^feat", group = "<!-- 0 -->🚀 Features" },
{ message = "^fix", group = "<!-- 1 -->🐛 Bug Fixes" },
{ message = "^doc", group = "<!-- 3 -->📚 Documentation" },
{ message = "^perf", group = "<!-- 4 -->⚡ Performance" },
{ message = "^refactor\\(clippy\\)", skip = true },
{ message = "^refactor", group = "<!-- 2 -->🚜 Refactor" },
{ message = "^style", group = "<!-- 5 -->🎨 Styling" },
{ message = "^test", group = "<!-- 6 -->🧪 Testing" },
{ message = "^chore: [rR]elease", skip = true },
{ message = "^\\(cargo-release\\)", skip = true },
{ message = "^chore\\(deps.*\\)", skip = true },
{ message = "^chore\\(pr\\)", skip = true },
{ message = "^chore\\(pull\\)", skip = true },
{ message = "^chore|^ci", group = "<!-- 7 -->⚙️ Miscellaneous Tasks" },
{ body = ".*security", group = "<!-- 8 -->🛡️ Security" },
{ message = "^revert", group = "<!-- 9 -->◀️ Revert" },
]
# protect breaking changes from being skipped due to matching a skipping commit_parser
protect_breaking_commits = false
# filter out the commits that are not matched by commit parsers
filter_commits = false
# regex for matching git tags
tag_pattern = "v[0-9].*"
# regex for skipping tags
skip_tags = "beta|alpha"
# regex for ignoring tags
ignore_tags = "rc|v2.1.0|v2.1.1"
# sort the tags topologically
topo_order = false
# sort the commits inside sections by oldest/newest order
sort_commits = "newest"

86
completions/bash/csview.bash generated Normal file
View File

@ -0,0 +1,86 @@
_csview() {
local i cur prev opts cmd
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
cmd=""
opts=""
for i in ${COMP_WORDS[@]}
do
case "${cmd},${i}" in
",$1")
cmd="csview"
;;
*)
;;
esac
done
case "${cmd}" in
csview)
opts="-H -n -t -d -s -p -i -P -h -V --no-headers --number --tsv --delimiter --style --padding --indent --sniff --header-align --body-align --disable-pager --help --version [FILE]"
if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
fi
case "${prev}" in
--delimiter)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
-d)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
--style)
COMPREPLY=($(compgen -W "none ascii ascii2 sharp rounded reinforced markdown grid" -- "${cur}"))
return 0
;;
-s)
COMPREPLY=($(compgen -W "none ascii ascii2 sharp rounded reinforced markdown grid" -- "${cur}"))
return 0
;;
--padding)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
-p)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
--indent)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
-i)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
--sniff)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
--header-align)
COMPREPLY=($(compgen -W "left center right" -- "${cur}"))
return 0
;;
--body-align)
COMPREPLY=($(compgen -W "left center right" -- "${cur}"))
return 0
;;
*)
COMPREPLY=()
;;
esac
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
;;
esac
}
if [[ "${BASH_VERSINFO[0]}" -eq 4 && "${BASH_VERSINFO[1]}" -ge 4 || "${BASH_VERSINFO[0]}" -gt 4 ]]; then
complete -F _csview -o nosort -o bashdefault -o default csview
else
complete -F _csview -o bashdefault -o default csview
fi

47
completions/elvish/csview.elv generated Normal file
View File

@ -0,0 +1,47 @@
use builtin;
use str;
set edit:completion:arg-completer[csview] = {|@words|
fn spaces {|n|
builtin:repeat $n ' ' | str:join ''
}
fn cand {|text desc|
edit:complex-candidate $text &display=$text' '(spaces (- 14 (wcswidth $text)))$desc
}
var command = 'csview'
for word $words[1..-1] {
if (str:has-prefix $word '-') {
break
}
set command = $command';'$word
}
var completions = [
&'csview'= {
cand -d 'Specify the field delimiter'
cand --delimiter 'Specify the field delimiter'
cand -s 'Specify the border style'
cand --style 'Specify the border style'
cand -p 'Specify padding for table cell'
cand --padding 'Specify padding for table cell'
cand -i 'Specify global indent for table'
cand --indent 'Specify global indent for table'
cand --sniff 'Limit column widths sniffing to the specified number of rows. Specify "0" to cancel limit'
cand --header-align 'Specify the alignment of the table header'
cand --body-align 'Specify the alignment of the table body'
cand -H 'Specify that the input has no header row'
cand --no-headers 'Specify that the input has no header row'
cand -n 'Prepend a column of line numbers to the table'
cand --number 'Prepend a column of line numbers to the table'
cand -t 'Use ''\t'' as delimiter for tsv'
cand --tsv 'Use ''\t'' as delimiter for tsv'
cand -P 'Disable pager'
cand --disable-pager 'Disable pager'
cand -h 'Print help'
cand --help 'Print help'
cand -V 'Print version'
cand --version 'Print version'
}
]
$completions[$command]
}

13
completions/fish/csview.fish generated Normal file
View File

@ -0,0 +1,13 @@
complete -c csview -s d -l delimiter -d 'Specify the field delimiter' -r
complete -c csview -s s -l style -d 'Specify the border style' -r -f -a "{none\t'',ascii\t'',ascii2\t'',sharp\t'',rounded\t'',reinforced\t'',markdown\t'',grid\t''}"
complete -c csview -s p -l padding -d 'Specify padding for table cell' -r
complete -c csview -s i -l indent -d 'Specify global indent for table' -r
complete -c csview -l sniff -d 'Limit column widths sniffing to the specified number of rows. Specify "0" to cancel limit' -r
complete -c csview -l header-align -d 'Specify the alignment of the table header' -r -f -a "{left\t'',center\t'',right\t''}"
complete -c csview -l body-align -d 'Specify the alignment of the table body' -r -f -a "{left\t'',center\t'',right\t''}"
complete -c csview -s H -l no-headers -d 'Specify that the input has no header row'
complete -c csview -s n -l number -d 'Prepend a column of line numbers to the table'
complete -c csview -s t -l tsv -d 'Use \'\\t\' as delimiter for tsv'
complete -c csview -s P -l disable-pager -d 'Disable pager'
complete -c csview -s h -l help -d 'Print help'
complete -c csview -s V -l version -d 'Print version'

53
completions/powershell/_csview.ps1 generated Normal file
View File

@ -0,0 +1,53 @@
using namespace System.Management.Automation
using namespace System.Management.Automation.Language
Register-ArgumentCompleter -Native -CommandName 'csview' -ScriptBlock {
param($wordToComplete, $commandAst, $cursorPosition)
$commandElements = $commandAst.CommandElements
$command = @(
'csview'
for ($i = 1; $i -lt $commandElements.Count; $i++) {
$element = $commandElements[$i]
if ($element -isnot [StringConstantExpressionAst] -or
$element.StringConstantType -ne [StringConstantType]::BareWord -or
$element.Value.StartsWith('-') -or
$element.Value -eq $wordToComplete) {
break
}
$element.Value
}) -join ';'
$completions = @(switch ($command) {
'csview' {
[CompletionResult]::new('-d', 'd', [CompletionResultType]::ParameterName, 'Specify the field delimiter')
[CompletionResult]::new('--delimiter', 'delimiter', [CompletionResultType]::ParameterName, 'Specify the field delimiter')
[CompletionResult]::new('-s', 's', [CompletionResultType]::ParameterName, 'Specify the border style')
[CompletionResult]::new('--style', 'style', [CompletionResultType]::ParameterName, 'Specify the border style')
[CompletionResult]::new('-p', 'p', [CompletionResultType]::ParameterName, 'Specify padding for table cell')
[CompletionResult]::new('--padding', 'padding', [CompletionResultType]::ParameterName, 'Specify padding for table cell')
[CompletionResult]::new('-i', 'i', [CompletionResultType]::ParameterName, 'Specify global indent for table')
[CompletionResult]::new('--indent', 'indent', [CompletionResultType]::ParameterName, 'Specify global indent for table')
[CompletionResult]::new('--sniff', 'sniff', [CompletionResultType]::ParameterName, 'Limit column widths sniffing to the specified number of rows. Specify "0" to cancel limit')
[CompletionResult]::new('--header-align', 'header-align', [CompletionResultType]::ParameterName, 'Specify the alignment of the table header')
[CompletionResult]::new('--body-align', 'body-align', [CompletionResultType]::ParameterName, 'Specify the alignment of the table body')
[CompletionResult]::new('-H', 'H ', [CompletionResultType]::ParameterName, 'Specify that the input has no header row')
[CompletionResult]::new('--no-headers', 'no-headers', [CompletionResultType]::ParameterName, 'Specify that the input has no header row')
[CompletionResult]::new('-n', 'n', [CompletionResultType]::ParameterName, 'Prepend a column of line numbers to the table')
[CompletionResult]::new('--number', 'number', [CompletionResultType]::ParameterName, 'Prepend a column of line numbers to the table')
[CompletionResult]::new('-t', 't', [CompletionResultType]::ParameterName, 'Use ''\t'' as delimiter for tsv')
[CompletionResult]::new('--tsv', 'tsv', [CompletionResultType]::ParameterName, 'Use ''\t'' as delimiter for tsv')
[CompletionResult]::new('-P', 'P ', [CompletionResultType]::ParameterName, 'Disable pager')
[CompletionResult]::new('--disable-pager', 'disable-pager', [CompletionResultType]::ParameterName, 'Disable pager')
[CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Print help')
[CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help')
[CompletionResult]::new('-V', 'V ', [CompletionResultType]::ParameterName, 'Print version')
[CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Print version')
break
}
})
$completions.Where{ $_.CompletionText -like "$wordToComplete*" } |
Sort-Object -Property ListItemText
}

55
completions/zsh/_csview generated Normal file
View File

@ -0,0 +1,55 @@
#compdef csview
autoload -U is-at-least
_csview() {
typeset -A opt_args
typeset -a _arguments_options
local ret=1
if is-at-least 5.2; then
_arguments_options=(-s -S -C)
else
_arguments_options=(-s -C)
fi
local context curcontext="$curcontext" state line
_arguments "${_arguments_options[@]}" : \
'-d+[Specify the field delimiter]:DELIMITER: ' \
'--delimiter=[Specify the field delimiter]:DELIMITER: ' \
'-s+[Specify the border style]:STYLE:(none ascii ascii2 sharp rounded reinforced markdown grid)' \
'--style=[Specify the border style]:STYLE:(none ascii ascii2 sharp rounded reinforced markdown grid)' \
'-p+[Specify padding for table cell]:PADDING: ' \
'--padding=[Specify padding for table cell]:PADDING: ' \
'-i+[Specify global indent for table]:INDENT: ' \
'--indent=[Specify global indent for table]:INDENT: ' \
'--sniff=[Limit column widths sniffing to the specified number of rows. Specify "0" to cancel limit]:LIMIT: ' \
'--header-align=[Specify the alignment of the table header]:HEADER_ALIGN:(left center right)' \
'--body-align=[Specify the alignment of the table body]:BODY_ALIGN:(left center right)' \
'-H[Specify that the input has no header row]' \
'--no-headers[Specify that the input has no header row]' \
'-n[Prepend a column of line numbers to the table]' \
'--number[Prepend a column of line numbers to the table]' \
'(-d --delimiter)-t[Use '\''\\t'\'' as delimiter for tsv]' \
'(-d --delimiter)--tsv[Use '\''\\t'\'' as delimiter for tsv]' \
'-P[Disable pager]' \
'--disable-pager[Disable pager]' \
'-h[Print help]' \
'--help[Print help]' \
'-V[Print version]' \
'--version[Print version]' \
'::FILE -- File to view:_files' \
&& ret=0
}
(( $+functions[_csview_commands] )) ||
_csview_commands() {
local commands; commands=()
_describe -t commands 'csview commands' commands "$@"
}
if [ "$funcstack[1]" = "_csview" ]; then
_csview "$@"
else
compdef _csview csview
fi

4
release.toml Normal file
View File

@ -0,0 +1,4 @@
allow-branch = ["master"]
pre-release-hook = ["sh", "-c", "git cliff -o CHANGELOG.md --tag {{version}} && git add CHANGELOG.md"]
pre-release-commit-message = "chore: release {{crate_name}} version {{version}}"
tag-message = "chore: release {{crate_name}} version {{version}}"

3
rust-toolchain.toml Normal file
View File

@ -0,0 +1,3 @@
[toolchain]
channel = "stable"
components = ["rustfmt", "clippy", "rust-analyzer"]

13
rustfmt.toml Normal file
View File

@ -0,0 +1,13 @@
edition = "2021"
max_width = 120
reorder_imports = true
struct_lit_width = 60
struct_variant_width = 60
# struct_field_align_threshold = 40
# comment_width = 120
# fn_single_line = false
# imports_layout = "HorizontalVertical"
# match_arm_blocks = false
# overflow_delimited_expr = true
# imports_granularity = "Crate"

77
src/cli.rs Normal file
View File

@ -0,0 +1,77 @@
use std::path::PathBuf;
use clap::{Parser, ValueEnum, ValueHint};
#[derive(Parser)]
#[command(about, version)]
#[command(disable_help_subcommand = true)]
#[command(next_line_help = true)]
pub struct App {
/// File to view.
#[arg(name = "FILE", value_hint = ValueHint::FilePath)]
pub file: Option<PathBuf>,
/// Specify that the input has no header row.
#[arg(short = 'H', long = "no-headers")]
pub no_headers: bool,
/// Prepend a column of line numbers to the table.
#[arg(short, long, alias = "seq")]
pub number: bool,
/// Use '\t' as delimiter for tsv.
#[arg(short, long, conflicts_with = "delimiter")]
pub tsv: bool,
/// Specify the field delimiter.
#[arg(short, long, default_value_t = ',')]
pub delimiter: char,
/// Specify the border style.
#[arg(short, long, value_enum, default_value_t = TableStyle::Sharp, ignore_case = true)]
pub style: TableStyle,
/// Specify padding for table cell.
#[arg(short, long, default_value_t = 1)]
pub padding: usize,
/// Specify global indent for table.
#[arg(short, long, default_value_t = 0)]
pub indent: usize,
/// Limit column widths sniffing to the specified number of rows. Specify "0" to cancel limit.
#[arg(long, default_value_t = 1000, name = "LIMIT")]
pub sniff: usize,
/// Specify the alignment of the table header.
#[arg(long, value_enum, default_value_t = Alignment::Center, ignore_case = true)]
pub header_align: Alignment,
/// Specify the alignment of the table body.
#[arg(long, value_enum, default_value_t = Alignment::Left, ignore_case = true)]
pub body_align: Alignment,
#[cfg(all(feature = "pager", unix))]
/// Disable pager.
#[arg(long, short = 'P')]
pub disable_pager: bool,
}
#[derive(Copy, Clone, ValueEnum)]
pub enum TableStyle {
None,
Ascii,
Ascii2,
Sharp,
Rounded,
Reinforced,
Markdown,
Grid,
}
#[derive(Copy, Clone, ValueEnum)]
pub enum Alignment {
Left,
Center,
Right,
}

102
src/main.rs Normal file
View File

@ -0,0 +1,102 @@
mod cli;
mod table;
mod util;
use anyhow::bail;
use clap::Parser;
use cli::App;
use csv::{ErrorKind, ReaderBuilder};
use std::{
fs::File,
io::{self, BufWriter, IsTerminal, Read},
process,
};
use table::TablePrinter;
use util::table_style;
#[cfg(all(feature = "pager", unix))]
use pager::Pager;
fn main() {
if let Err(e) = try_main() {
if let Some(ioerr) = e.root_cause().downcast_ref::<io::Error>() {
if ioerr.kind() == io::ErrorKind::BrokenPipe {
process::exit(exitcode::OK);
}
}
if let Some(csverr) = e.root_cause().downcast_ref::<csv::Error>() {
match csverr.kind() {
ErrorKind::Utf8 { .. } => {
eprintln!("[error] input is not utf8 encoded");
process::exit(exitcode::DATAERR)
}
ErrorKind::UnequalLengths { pos, expected_len, len } => {
let pos_info = pos
.as_ref()
.map(|p| format!(" at (byte: {}, line: {}, record: {})", p.byte(), p.line(), p.record()))
.unwrap_or_else(|| "".to_string());
eprintln!(
"[error] unequal lengths{}: expected length is {}, but got {}",
pos_info, expected_len, len
);
process::exit(exitcode::DATAERR)
}
ErrorKind::Io(e) => {
eprintln!("[error] io error: {}", e);
process::exit(exitcode::IOERR)
}
e => {
eprintln!("[error] failed to process input: {:?}", e);
process::exit(exitcode::DATAERR)
}
}
}
eprintln!("{}: {}", env!("CARGO_PKG_NAME"), e);
std::process::exit(1)
}
}
fn try_main() -> anyhow::Result<()> {
let App {
file,
no_headers,
number,
tsv,
delimiter,
style,
padding,
indent,
sniff,
header_align,
body_align,
#[cfg(all(feature = "pager", unix))]
disable_pager,
} = App::parse();
#[cfg(all(feature = "pager", unix))]
if !disable_pager && io::stdout().is_terminal() {
match std::env::var("CSVIEW_PAGER") {
Ok(pager) => Pager::with_pager(&pager).setup(),
// XXX: the extra null byte can be removed once https://gitlab.com/imp/pager-rs/-/merge_requests/8 is merged
Err(_) => Pager::with_pager("less").pager_envs(["LESS=-SF\0"]).setup(),
}
}
let stdout = io::stdout();
let wtr = &mut BufWriter::new(stdout.lock());
let rdr = ReaderBuilder::new()
.delimiter(if tsv { b'\t' } else { delimiter as u8 })
.has_headers(!no_headers)
.from_reader(match file {
Some(path) => Box::new(File::open(path)?) as Box<dyn Read>,
None if io::stdin().is_terminal() => bail!("no input file specified (use -h for help)"),
None => Box::new(io::stdin()),
});
let sniff = if sniff == 0 { usize::MAX } else { sniff };
let table = TablePrinter::new(rdr, sniff, number)?;
table.writeln(wtr, &table_style(style, padding, indent, header_align, body_align))?;
Ok(())
}

6
src/table/mod.rs Normal file
View File

@ -0,0 +1,6 @@
mod printer;
mod row;
mod style;
pub use printer::TablePrinter;
pub use style::{RowSep, Style, StyleBuilder};

260
src/table/printer.rs Normal file
View File

@ -0,0 +1,260 @@
use super::{row::Row, style::Style};
use csv::{Reader, StringRecord};
use std::io::{self, Result, Write};
use unicode_width::UnicodeWidthStr;
pub struct TablePrinter {
header: Option<StringRecord>,
widths: Vec<usize>,
records: Box<dyn Iterator<Item = csv::Result<StringRecord>>>,
with_seq: bool,
}
impl TablePrinter {
pub(crate) fn new<R: 'static + io::Read>(mut rdr: Reader<R>, sniff_rows: usize, with_seq: bool) -> Result<Self> {
let header = rdr.has_headers().then(|| rdr.headers()).transpose()?.cloned();
let (widths, buf) = sniff_widths(&mut rdr, header.as_ref(), sniff_rows, with_seq)?;
let records = Box::new(buf.into_iter().map(Ok).chain(rdr.into_records()));
Ok(Self { header, widths, records, with_seq })
}
pub(crate) fn writeln<W: Write>(self, wtr: &mut W, fmt: &Style) -> Result<()> {
let widths = &self.widths;
fmt.rowseps
.top
.map(|sep| fmt.write_row_sep(wtr, widths, &sep))
.transpose()?;
let mut iter = self.records.peekable();
if let Some(header) = self.header {
let row: Row = self.with_seq.then_some("#").into_iter().chain(header.iter()).collect();
row.writeln(wtr, fmt, widths, fmt.header_align)?;
if iter.peek().is_some() {
fmt.rowseps
.snd
.map(|sep| fmt.write_row_sep(wtr, widths, &sep))
.transpose()?;
}
}
let mut seq = 1;
while let Some(record) = iter.next().transpose()? {
let seq_str = self.with_seq.then(|| seq.to_string());
let row: Row = seq_str.iter().map(|s| s.as_str()).chain(record.into_iter()).collect();
row.writeln(wtr, fmt, widths, fmt.body_align)?;
if let Some(mid) = &fmt.rowseps.mid {
if iter.peek().is_some() {
fmt.write_row_sep(wtr, widths, mid)?;
}
}
seq += 1;
}
fmt.rowseps
.bot
.map(|sep| fmt.write_row_sep(wtr, widths, &sep))
.transpose()?;
wtr.flush()
}
}
fn sniff_widths<R: io::Read>(
rdr: &mut Reader<R>,
header: Option<&StringRecord>,
sniff_rows: usize,
with_seq: bool,
) -> Result<(Vec<usize>, Vec<StringRecord>)> {
let mut widths = Vec::new();
let mut buf = Vec::new();
fn update_widths(record: &StringRecord, widths: &mut Vec<usize>) {
widths.resize(record.len(), 0);
record
.into_iter()
.map(UnicodeWidthStr::width_cjk)
.enumerate()
.for_each(|(i, width)| widths[i] = widths[i].max(width))
}
let mut record = header.cloned().unwrap_or_default();
update_widths(&record, &mut widths);
let mut seq = 1;
while seq <= sniff_rows && rdr.read_record(&mut record)? {
update_widths(&record, &mut widths);
seq += 1;
buf.push(record.clone());
}
if with_seq {
widths.insert(0, seq.to_string().width());
}
Ok((widths, buf))
}
#[cfg(test)]
mod test {
use super::*;
use crate::table::{RowSep, StyleBuilder};
use anyhow::Result;
use csv::ReaderBuilder;
macro_rules! gen_table {
($($line:expr)*) => {
concat!(
$($line, "\n",)*
)
};
}
#[test]
fn test_write() -> Result<()> {
let text = "a,b,c\n1,2,3\n4,5,6";
let rdr = ReaderBuilder::new().has_headers(true).from_reader(text.as_bytes());
let wtr = TablePrinter::new(rdr, 3, false)?;
let mut buf = Vec::new();
wtr.writeln(&mut buf, &Style::default())?;
assert_eq!(
gen_table!(
"+---+---+---+"
"| a | b | c |"
"+---+---+---+"
"| 1 | 2 | 3 |"
"+---+---+---+"
"| 4 | 5 | 6 |"
"+---+---+---+"
),
std::str::from_utf8(&buf)?
);
Ok(())
}
#[test]
fn test_write_without_padding() -> Result<()> {
let text = "a,b,c\n1,2,3\n4,5,6";
let rdr = ReaderBuilder::new().has_headers(true).from_reader(text.as_bytes());
let wtr = TablePrinter::new(rdr, 3, false)?;
let fmt = StyleBuilder::default().padding(0).build();
let mut buf = Vec::new();
wtr.writeln(&mut buf, &fmt)?;
assert_eq!(
gen_table!(
"+-+-+-+"
"|a|b|c|"
"+-+-+-+"
"|1|2|3|"
"+-+-+-+"
"|4|5|6|"
"+-+-+-+"
),
std::str::from_utf8(&buf)?
);
Ok(())
}
#[test]
fn test_write_with_indent() -> Result<()> {
let text = "a,b,c\n1,2,3\n4,5,6";
let rdr = ReaderBuilder::new().has_headers(true).from_reader(text.as_bytes());
let wtr = TablePrinter::new(rdr, 3, false)?;
let fmt = StyleBuilder::default().indent(4).build();
let mut buf = Vec::new();
wtr.writeln(&mut buf, &fmt)?;
assert_eq!(
gen_table!(
" +---+---+---+"
" | a | b | c |"
" +---+---+---+"
" | 1 | 2 | 3 |"
" +---+---+---+"
" | 4 | 5 | 6 |"
" +---+---+---+"
),
std::str::from_utf8(&buf)?
);
Ok(())
}
#[test]
fn test_only_header() -> Result<()> {
let text = "a,ab,abc";
let rdr = ReaderBuilder::new().has_headers(true).from_reader(text.as_bytes());
let wtr = TablePrinter::new(rdr, 3, false)?;
let fmt = Style::default();
let mut buf = Vec::new();
wtr.writeln(&mut buf, &fmt)?;
assert_eq!(
gen_table!(
"+---+----+-----+"
"| a | ab | abc |"
"+---+----+-----+"
),
std::str::from_utf8(&buf)?
);
Ok(())
}
#[test]
fn test_without_header() -> Result<()> {
let text = "1,123,35\n383,2, 17";
let rdr = ReaderBuilder::new().has_headers(false).from_reader(text.as_bytes());
let wtr = TablePrinter::new(rdr, 3, false)?;
let fmt = StyleBuilder::new()
.col_sep('│')
.row_seps(
RowSep::new('─', '╭', '┬', '╮'),
RowSep::new('─', '├', '┼', '┤'),
None,
RowSep::new('─', '╰', '┴', '╯'),
)
.build();
let mut buf = Vec::new();
wtr.writeln(&mut buf, &fmt)?;
assert_eq!(
gen_table!(
"╭─────┬─────┬─────╮"
"│ 1 │ 123 │ 35 │"
"│ 383 │ 2 │ 17 │"
"╰─────┴─────┴─────╯"
),
std::str::from_utf8(&buf)?
);
Ok(())
}
#[test]
fn test_with_seq() -> Result<()> {
let text = "a,b,c\n1,2,3\n4,5,6";
let rdr = ReaderBuilder::new().has_headers(true).from_reader(text.as_bytes());
let wtr = TablePrinter::new(rdr, 3, true)?;
let mut buf = Vec::new();
wtr.writeln(&mut buf, &Style::default())?;
assert_eq!(
gen_table!(
"+---+---+---+---+"
"| # | a | b | c |"
"+---+---+---+---+"
"| 1 | 1 | 2 | 3 |"
"+---+---+---+---+"
"| 2 | 4 | 5 | 6 |"
"+---+---+---+---+"
),
std::str::from_utf8(&buf)?
);
Ok(())
}
}

96
src/table/row.rs Normal file
View File

@ -0,0 +1,96 @@
use std::io::{Result, Write};
use itertools::Itertools;
use unicode_truncate::{Alignment, UnicodeTruncateStr};
use crate::table::Style;
/// Represent a table row made of cells
#[derive(Clone, Debug)]
pub struct Row<'a> {
cells: Vec<&'a str>,
}
impl<'a> FromIterator<&'a str> for Row<'a> {
fn from_iter<I: IntoIterator<Item = &'a str>>(iter: I) -> Self {
Self { cells: iter.into_iter().collect() }
}
}
impl<'a> Row<'a> {
pub fn write<T: Write>(&self, wtr: &mut T, fmt: &Style, widths: &[usize], align: Alignment) -> Result<()> {
let sep = fmt.colseps.mid.map(|c| c.to_string()).unwrap_or_default();
write!(wtr, "{:indent$}", "", indent = fmt.indent)?;
fmt.colseps.lhs.map(|sep| fmt.write_col_sep(wtr, sep)).transpose()?;
Itertools::intersperse(
self.cells
.iter()
.zip(widths)
.map(|(cell, &width)| cell.unicode_pad(width, align, true))
.map(|s| format!("{:pad$}{}{:pad$}", "", s, "", pad = fmt.padding)),
sep,
)
.try_for_each(|s| write!(wtr, "{}", s))?;
fmt.colseps.rhs.map(|sep| fmt.write_col_sep(wtr, sep)).transpose()?;
Ok(())
}
pub fn writeln<T: Write>(&self, wtr: &mut T, fmt: &Style, widths: &[usize], align: Alignment) -> Result<()> {
self.write(wtr, fmt, widths, align).and_then(|_| writeln!(wtr))
}
}
#[cfg(test)]
mod test {
use super::*;
use anyhow::Result;
use std::str;
#[test]
fn write_ascii_row() -> Result<()> {
let row = Row::from_iter(["a", "b"]);
let buf = &mut Vec::new();
let fmt = Style::default();
let widths = [3, 4];
row.writeln(buf, &fmt, &widths, Alignment::Left)?;
assert_eq!("| a | b |\n", str::from_utf8(buf)?);
Ok(())
}
#[test]
fn write_cjk_row() -> Result<()> {
let row = Row::from_iter(["李磊(Jack)", "四川省成都市", "💍"]);
let buf = &mut Vec::new();
let fmt = Style::default();
let widths = [10, 8, 2];
row.writeln(buf, &fmt, &widths, Alignment::Left)?;
assert_eq!("| 李磊(Jack) | 四川省成 | 💍 |\n", str::from_utf8(buf)?);
Ok(())
}
#[test]
fn write_align_center() -> Result<()> {
let row = Row::from_iter(["a", "b"]);
let buf = &mut Vec::new();
let fmt = Style::default();
let widths = [3, 4];
row.writeln(buf, &fmt, &widths, Alignment::Center)?;
assert_eq!("| a | b |\n", str::from_utf8(buf)?);
Ok(())
}
#[test]
fn write_align_right() -> Result<()> {
let row = Row::from_iter(["a", "b"]);
let buf = &mut Vec::new();
let fmt = Style::default();
let widths = [3, 4];
row.writeln(buf, &fmt, &widths, Alignment::Right)?;
assert_eq!("| a | b |\n", str::from_utf8(buf)?);
Ok(())
}
}

277
src/table/style.rs Normal file
View File

@ -0,0 +1,277 @@
use std::io::{Result, Write};
use unicode_truncate::Alignment;
#[derive(Debug, Clone, Copy)]
pub struct RowSeps {
/// Top separator row (top border)
/// ```
/// >┌───┬───┐
/// │ a │ b │
/// ```
pub top: Option<RowSep>,
/// Second separator row (between the header row and the first data row)
/// ```
/// ┌───┬───┐
/// │ a │ b │
/// >├───┼───┤
/// ```
pub snd: Option<RowSep>,
/// Middle separator row (between data rows)
/// ```
/// >├───┼───┤
/// │ 2 │ 2 │
/// >├───┼───┤
/// ```
pub mid: Option<RowSep>,
/// Bottom separator row (bottom border)
/// ```
/// │ 3 │ 3 │
/// >└───┴───┘
/// ```
pub bot: Option<RowSep>,
}
/// The characters used for printing a row separator
#[derive(Debug, Clone, Copy)]
pub struct RowSep {
/// Inner row separator
/// ```
/// ┌───┬───┐
/// ^
/// ```
inner: char,
/// Left junction separator
/// ```
/// ┌───┬───┐
/// ^
/// ```
ljunc: char,
/// Crossing junction separator
/// ```
/// ┌───┬───┐
/// ^
/// ```
cjunc: char,
/// Right junction separator
/// ```
/// ┌───┬───┐
/// ^
/// ```
rjunc: char,
}
#[derive(Debug, Clone, Copy)]
pub struct ColSeps {
/// Left separator column (left border)
/// ```
/// │ 1 │ 2 │
/// ^
/// ```
pub lhs: Option<char>,
/// Middle column separators
/// ```
/// │ 1 │ 2 │
/// ^
/// ```
pub mid: Option<char>,
/// Right separator column (right border)
/// ```
/// │ 1 │ 2 │
/// ^
/// ```
pub rhs: Option<char>,
}
impl RowSep {
pub fn new(sep: char, ljunc: char, cjunc: char, rjunc: char) -> RowSep {
RowSep { inner: sep, ljunc, cjunc, rjunc }
}
}
impl Default for RowSep {
fn default() -> Self {
RowSep::new('-', '+', '+', '+')
}
}
impl Default for RowSeps {
fn default() -> Self {
Self {
top: Some(RowSep::default()),
snd: Some(RowSep::default()),
mid: Some(RowSep::default()),
bot: Some(RowSep::default()),
}
}
}
impl Default for ColSeps {
fn default() -> Self {
Self { lhs: Some('|'), mid: Some('|'), rhs: Some('|') }
}
}
#[derive(Debug, Clone, Copy)]
pub struct Style {
/// Column style
pub colseps: ColSeps,
/// Row style
pub rowseps: RowSeps,
/// Left and right padding
pub padding: usize,
/// Global indentation
pub indent: usize,
/// Header alignment
pub header_align: Alignment,
/// Data alignment
pub body_align: Alignment,
}
impl Default for Style {
fn default() -> Self {
Self {
indent: 0,
padding: 1,
colseps: ColSeps::default(),
rowseps: RowSeps::default(),
header_align: Alignment::Center,
body_align: Alignment::Left,
}
}
}
impl Style {
pub fn write_row_sep<W: Write>(&self, wtr: &mut W, widths: &[usize], sep: &RowSep) -> Result<()> {
write!(wtr, "{:indent$}", "", indent = self.indent)?;
if self.colseps.lhs.is_some() {
write!(wtr, "{}", sep.ljunc)?;
}
let mut iter = widths.iter().peekable();
while let Some(width) = iter.next() {
for _ in 0..width + self.padding * 2 {
write!(wtr, "{}", sep.inner)?;
}
if self.colseps.mid.is_some() && iter.peek().is_some() {
write!(wtr, "{}", sep.cjunc)?;
}
}
if self.colseps.rhs.is_some() {
write!(wtr, "{}", sep.rjunc)?;
}
writeln!(wtr)
}
#[inline]
pub fn write_col_sep<W: Write>(&self, wtr: &mut W, sep: char) -> Result<()> {
write!(wtr, "{}", sep)
}
}
#[derive(Default, Debug, Clone)]
pub struct StyleBuilder {
format: Box<Style>,
}
impl StyleBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn padding(mut self, padding: usize) -> Self {
self.format.padding = padding;
self
}
pub fn col_sep(self, sep: impl Into<Option<char>>) -> Self {
let sep = sep.into();
self.col_seps(sep, sep, sep)
}
pub fn col_seps<L, M, R>(mut self, lhs: L, mid: M, rhs: R) -> Self
where
L: Into<Option<char>>,
M: Into<Option<char>>,
R: Into<Option<char>>,
{
self.format.colseps = ColSeps { lhs: lhs.into(), mid: mid.into(), rhs: rhs.into() };
self
}
pub fn row_seps<S1, S2, S3, S4>(mut self, top: S1, snd: S2, mid: S3, bot: S4) -> Self
where
S1: Into<Option<RowSep>>,
S2: Into<Option<RowSep>>,
S3: Into<Option<RowSep>>,
S4: Into<Option<RowSep>>,
{
self.format.rowseps = RowSeps {
top: top.into(),
snd: snd.into(),
mid: mid.into(),
bot: bot.into(),
};
self
}
pub fn clear_seps(self) -> Self {
self.col_seps(None, None, None).row_seps(None, None, None, None)
}
pub fn indent(mut self, indent: usize) -> Self {
self.format.indent = indent;
self
}
pub fn header_align(mut self, align: Alignment) -> Self {
self.format.header_align = align;
self
}
pub fn body_align(mut self, align: Alignment) -> Self {
self.format.body_align = align;
self
}
pub fn build(&self) -> Style {
*self.format
}
}
#[cfg(test)]
mod test {
use super::*;
use anyhow::Result;
use std::str;
#[test]
fn test_write_column_separator() -> Result<()> {
let fmt = StyleBuilder::new().col_seps('|', '|', '|').padding(1).build();
let buf = &mut Vec::new();
fmt.colseps.lhs.map(|sep| fmt.write_col_sep(buf, sep)).transpose()?;
assert_eq!("|", str::from_utf8(buf)?);
Ok(())
}
#[test]
fn test_write_row_separator() -> Result<()> {
let fmt = StyleBuilder::new().indent(4).build();
let buf = &mut Vec::new();
let widths = &[2, 4, 6];
fmt.rowseps
.top
.map(|sep| fmt.write_row_sep(buf, widths, &sep))
.transpose()?;
assert_eq!(" +----+------+--------+\n", str::from_utf8(buf)?);
Ok(())
}
}

74
src/util.rs Normal file
View File

@ -0,0 +1,74 @@
use cli::Alignment;
use crate::{
cli::{self, TableStyle},
table::{RowSep, Style, StyleBuilder},
};
pub fn table_style(
style: TableStyle,
padding: usize,
indent: usize,
header_align: Alignment,
body_align: Alignment,
) -> Style {
let builder = match style {
TableStyle::None => StyleBuilder::new().clear_seps(),
TableStyle::Ascii => StyleBuilder::new().col_sep('|').row_seps(
RowSep::new('-', '+', '+', '+'),
RowSep::new('-', '+', '+', '+'),
None,
RowSep::new('-', '+', '+', '+'),
),
TableStyle::Ascii2 => {
StyleBuilder::new()
.col_seps(' ', '|', ' ')
.row_seps(None, RowSep::new('-', ' ', '+', ' '), None, None)
}
TableStyle::Sharp => StyleBuilder::new().col_sep('│').row_seps(
RowSep::new('─', '┌', '┬', '┐'),
RowSep::new('─', '├', '┼', '┤'),
None,
RowSep::new('─', '└', '┴', '┘'),
),
TableStyle::Rounded => StyleBuilder::new().col_sep('│').row_seps(
RowSep::new('─', '╭', '┬', '╮'),
RowSep::new('─', '├', '┼', '┤'),
None,
RowSep::new('─', '╰', '┴', '╯'),
),
TableStyle::Reinforced => StyleBuilder::new().col_sep('│').row_seps(
RowSep::new('─', '┏', '┬', '┓'),
RowSep::new('─', '├', '┼', '┤'),
None,
RowSep::new('─', '┗', '┴', '┛'),
),
TableStyle::Markdown => {
StyleBuilder::new()
.col_sep('|')
.row_seps(None, RowSep::new('-', '|', '|', '|'), None, None)
}
TableStyle::Grid => StyleBuilder::new().col_sep('│').row_seps(
RowSep::new('─', '┌', '┬', '┐'),
RowSep::new('─', '├', '┼', '┤'),
RowSep::new('─', '├', '┼', '┤'),
RowSep::new('─', '└', '┴', '┘'),
),
};
builder
.padding(padding)
.indent(indent)
.header_align(header_align.into())
.body_align(body_align.into())
.build()
}
impl From<Alignment> for unicode_truncate::Alignment {
fn from(a: Alignment) -> Self {
match a {
Alignment::Left => unicode_truncate::Alignment::Left,
Alignment::Center => unicode_truncate::Alignment::Center,
Alignment::Right => unicode_truncate::Alignment::Right,
}
}
}