commit 1c8e2579b94ce236abde79a59e5c9dbe94c213d9 Author: JIe Date: Wed Nov 20 11:44:36 2024 +0800 init diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..febe51d --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +/completions/**/* linguist-generated diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..93d9249 --- /dev/null +++ b/.github/dependabot.yml @@ -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" diff --git a/.github/issue_template.md b/.github/issue_template.md new file mode 100644 index 0000000..bed735f --- /dev/null +++ b/.github/issue_template.md @@ -0,0 +1,22 @@ + + + + +## 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 + + + +## Problem / Steps to reproduce diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..55b0090 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,29 @@ + + +## 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 + + + +## Type of change + +- [ ] Bug fix +- [ ] New feature +- [ ] Refactor +- [ ] Breaking change +- [ ] Documentation change +- [ ] CICD related improvement + +## Test environment + +- OS + - [ ] Linux + - [ ] Mac OS X + - [ ] Windows + - [ ] Others: diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml new file mode 100644 index 0000000..a2a5785 --- /dev/null +++ b/.github/workflows/CICD.yml @@ -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" < "${DPKG_DIR}/DEBIAN/control" <> $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 }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..118b953 --- /dev/null +++ b/.github/workflows/release.yml @@ -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 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2c882fb --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/target +/.idea +/flamegraph.svg +/perf.data diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..01935cf --- /dev/null +++ b/.pre-commit-config.yaml @@ -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 + ' diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..7e0898e --- /dev/null +++ b/CHANGELOG.md @@ -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)) + + diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..c87f6d9 --- /dev/null +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..7e932e9 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "csview" +version = "1.3.3" +authors = ["Wenxuan Zhang "] +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 diff --git a/FUNDING.yml b/FUNDING.yml new file mode 100644 index 0000000..63b4a7e --- /dev/null +++ b/FUNDING.yml @@ -0,0 +1 @@ +ko_fi: wfxr diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE-APACHE @@ -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. diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 0000000..6eac644 --- /dev/null +++ b/LICENSE-MIT @@ -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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..dc20ed7 --- /dev/null +++ b/README.md @@ -0,0 +1,190 @@ +

๐Ÿ“  csview

+

+ A high performance csv viewer with cjk/emoji support. +

+ +

+ + CICD + + License + + Version + + + Platform + +

+ + + +### 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. diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..7436e59 --- /dev/null +++ b/build.rs @@ -0,0 +1,20 @@ +use clap::CommandFactory; +use clap_complete::Shell; +use std::{fs, path::Path}; + +include!("src/cli.rs"); + +fn main() -> Result<(), Box> { + 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(()) +} diff --git a/cliff.toml b/cliff.toml new file mode 100644 index 0000000..2809a1c --- /dev/null +++ b/cliff.toml @@ -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 = """ + +""" +# remove the leading and trailing whitespace from the templates +trim = true +# postprocessors +postprocessors = [ + { pattern = '', 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}](/issues/${2}))" }, +] +# regex for parsing and grouping commits +commit_parsers = [ + { message = "^feat", group = "๐Ÿš€ Features" }, + { message = "^fix", group = "๐Ÿ› Bug Fixes" }, + { message = "^doc", group = "๐Ÿ“š Documentation" }, + { message = "^perf", group = "โšก Performance" }, + { message = "^refactor\\(clippy\\)", skip = true }, + { message = "^refactor", group = "๐Ÿšœ Refactor" }, + { message = "^style", group = "๐ŸŽจ Styling" }, + { message = "^test", group = "๐Ÿงช 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 = "โš™๏ธ Miscellaneous Tasks" }, + { body = ".*security", group = "๐Ÿ›ก๏ธ Security" }, + { message = "^revert", group = "โ—€๏ธ 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" diff --git a/completions/bash/csview.bash b/completions/bash/csview.bash new file mode 100644 index 0000000..de865ad --- /dev/null +++ b/completions/bash/csview.bash @@ -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 diff --git a/completions/elvish/csview.elv b/completions/elvish/csview.elv new file mode 100644 index 0000000..5f54505 --- /dev/null +++ b/completions/elvish/csview.elv @@ -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] +} diff --git a/completions/fish/csview.fish b/completions/fish/csview.fish new file mode 100644 index 0000000..3c6a40a --- /dev/null +++ b/completions/fish/csview.fish @@ -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' diff --git a/completions/powershell/_csview.ps1 b/completions/powershell/_csview.ps1 new file mode 100644 index 0000000..9457229 --- /dev/null +++ b/completions/powershell/_csview.ps1 @@ -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 +} diff --git a/completions/zsh/_csview b/completions/zsh/_csview new file mode 100644 index 0000000..eade7e4 --- /dev/null +++ b/completions/zsh/_csview @@ -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 diff --git a/release.toml b/release.toml new file mode 100644 index 0000000..c1e0235 --- /dev/null +++ b/release.toml @@ -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}}" diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..3fe7418 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,3 @@ +[toolchain] +channel = "stable" +components = ["rustfmt", "clippy", "rust-analyzer"] diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..0bcc3d2 --- /dev/null +++ b/rustfmt.toml @@ -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" diff --git a/src/cli.rs b/src/cli.rs new file mode 100644 index 0000000..5344654 --- /dev/null +++ b/src/cli.rs @@ -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, + + /// 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, +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..8251e18 --- /dev/null +++ b/src/main.rs @@ -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::() { + if ioerr.kind() == io::ErrorKind::BrokenPipe { + process::exit(exitcode::OK); + } + } + + if let Some(csverr) = e.root_cause().downcast_ref::() { + 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, + 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(()) +} diff --git a/src/table/mod.rs b/src/table/mod.rs new file mode 100644 index 0000000..0a5513e --- /dev/null +++ b/src/table/mod.rs @@ -0,0 +1,6 @@ +mod printer; +mod row; +mod style; + +pub use printer::TablePrinter; +pub use style::{RowSep, Style, StyleBuilder}; diff --git a/src/table/printer.rs b/src/table/printer.rs new file mode 100644 index 0000000..8c3e56a --- /dev/null +++ b/src/table/printer.rs @@ -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, + widths: Vec, + records: Box>>, + with_seq: bool, +} + +impl TablePrinter { + pub(crate) fn new(mut rdr: Reader, sniff_rows: usize, with_seq: bool) -> Result { + 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(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( + rdr: &mut Reader, + header: Option<&StringRecord>, + sniff_rows: usize, + with_seq: bool, +) -> Result<(Vec, Vec)> { + let mut widths = Vec::new(); + let mut buf = Vec::new(); + + fn update_widths(record: &StringRecord, widths: &mut Vec) { + 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(()) + } +} diff --git a/src/table/row.rs b/src/table/row.rs new file mode 100644 index 0000000..4d60867 --- /dev/null +++ b/src/table/row.rs @@ -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>(iter: I) -> Self { + Self { cells: iter.into_iter().collect() } + } +} + +impl<'a> Row<'a> { + pub fn 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(&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(()) + } +} diff --git a/src/table/style.rs b/src/table/style.rs new file mode 100644 index 0000000..9481941 --- /dev/null +++ b/src/table/style.rs @@ -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, + /// Second separator row (between the header row and the first data row) + /// ``` + /// โ”Œโ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ” + /// โ”‚ a โ”‚ b โ”‚ + /// >โ”œโ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”ค + /// ``` + pub snd: Option, + /// Middle separator row (between data rows) + /// ``` + /// >โ”œโ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”ค + /// โ”‚ 2 โ”‚ 2 โ”‚ + /// >โ”œโ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”ค + /// ``` + pub mid: Option, + /// Bottom separator row (bottom border) + /// ``` + /// โ”‚ 3 โ”‚ 3 โ”‚ + /// >โ””โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”˜ + /// ``` + pub bot: Option, +} + +/// 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, + /// Middle column separators + /// ``` + /// โ”‚ 1 โ”‚ 2 โ”‚ + /// ^ + /// ``` + pub mid: Option, + /// Right separator column (right border) + /// ``` + /// โ”‚ 1 โ”‚ 2 โ”‚ + /// ^ + /// ``` + pub rhs: Option, +} + +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(&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(&self, wtr: &mut W, sep: char) -> Result<()> { + write!(wtr, "{}", sep) + } +} + +#[derive(Default, Debug, Clone)] +pub struct StyleBuilder { + format: Box