hexyl-0.8.0/.cargo_vcs_info.json0000644000000001121366716645300123030ustar00{ "git": { "sha1": "f8f410b8ee39ad083700b308495d2c025e522b25" } } hexyl-0.8.0/.gitignore010064400017500001735000000000231337011776200130660ustar0000000000000000/target **/*.rs.bk hexyl-0.8.0/.travis.yml010064400017500001735000000054231366716403400132230ustar0000000000000000language: rust cache: cargo matrix: include: # Stable channel. - os: linux rust: stable env: TARGET=x86_64-unknown-linux-gnu - os: linux rust: stable env: TARGET=x86_64-unknown-linux-musl - os: osx rust: stable env: TARGET=x86_64-apple-darwin - os: windows rust: stable env: TARGET=x86_64-pc-windows-gnu # Minimum Rust supported channel. - os: linux rust: 1.36.0 env: TARGET=x86_64-unknown-linux-gnu addons: apt: packages: # needed to build deb packages - fakeroot env: global: # Default target on travis-ci. # Used as conditional check in the install stage - HOST=x86_64-unknown-linux-gnu # Used on the deployment script - PROJECT_NAME=hexyl install: # prevent target re-add error from rustup - if [[ $TRAVIS_OS_NAME = linux && $HOST != $TARGET ]]; then rustup target add $TARGET; fi script: # Incorporate TARGET env var to the build and test process - cargo build --target $TARGET --verbose - cargo test --target $TARGET --verbose - cargo run --target $TARGET --example=simple before_deploy: - bash ci/before_deploy.bash deploy: provider: releases # NOTE updating the `api_key.secure` # - go to: https://github.com/settings/tokens/new # - generate new token using `public_repo` scope # - encrypt it using: `travis encrypt API_KEY_HERE` # - paste the output below api_key: secure: "XHpyp21JUWI6GkV7APTwPZiJQJkRJ13E/9VALuWvDrNYF00OE8m+T0ZbG+N1WCuwewa2QQChqd5yBDlwJn+LlDPqstie69xjCt7PEhvI5ERZlbT8KUtMLI4bFIudsaa6LS6phWi/6aKb4cfn0nEYmWhpqfVfsn4Hh4F9OyOFSRmMSSXziU3kxLdIBBO4jOfoDYz+4KUX48CtIhmFy6dSb4G22jzVXL3ixGq895lVFlUzfiGJhHXoS+6KsOx1q0eIEMPC6V0VTT1loBNxKpjBPQ1b0KysCim/jRnr9mB3ko/4yWcMtIvCaZxTaQXWsQn9wAEN62x3ruKugZh3LJbtaxSvnH9PMntXZliz1HJw3Hpmn+tGLxAOm+uwHRz2C/T2ibemgmw6F8KjsjdNv7Nc2//ErIbmMvV4OlJjJI2Q3LWjnYa2gqI0L0sMqUz9HAfiY/BXLSxp19LgSvZPuHNVzMfh9ovF+Rdk1I3TyoByS8ZUi2ngnra8bSjmjtWHmkntOksjWNG6iobpjz9UxdWXPWxNJHQbRfEyTJSISpClykcSZWj+E2+tfuB3YvuRUaVOIhA3THdOSsqEesl3jkUeIhQqgRhE+Uq/XqB7MdfZ9ZTX8ZgfGFL0gjygviuWznTb0q5i4t1SgEJ2ie+ykjVyqQWa0dePtaQp8yx2PtzchJs=" # for uploading multiple files file_glob: true # NOTE explanation on each env variable # - PROJECT_NAME: name of the project, set on the `env.global` above # - TRAVIS_TAG: tag name that the build is being deployed for, usually the version number # - TARGET: target triple of the build file: - $PROJECT_NAME-$TRAVIS_TAG-$TARGET.* - $PROJECT_NAME*.deb # don't delete artifacts from previous stage skip_cleanup: true on: # deploy only if we push a tag tags: true # deploy only on stable channel that has TARGET env variable sets condition: $TRAVIS_RUST_VERSION = stable && $TARGET != "x86_64-pc-windows-gnu" notifications: email: on_success: never hexyl-0.8.0/CHANGELOG.md010064400017500001735000000057711366716371600127370ustar0000000000000000# v0.8.0 ## Features - A new `--skip ` / `-s ` option can be used to skip the first `N` bytes of the input, see #16, #88 (@Tarnadas, @MaxJohansen, @ErichDonGubler) - The `--length`/`--bytes`/`--skip`/`--display-offset` options can now take units for their value argument, for example: ``` bash hexyl /dev/random --length=1KiB hexyl $(which hexyl) --skip=1MiB --length=10KiB ``` Both decimal SI prefixes (kB, MB, …) as well as binary IEC prefixes (KiB, MiB, …) are supported. In addition, there is a new `--block-size ` option that can be used to control the size of the `block` unit: ``` bash hexyl /dev/random --block-size=4kB --length=2block ``` See: #44 (@ErichDonGubler and @aswild) ## Other - Various improvements throughout the code base by @ErichDonGubler ## Packaging - `hexyl` is now available on Void Linux, see #91 (@notramo) # v0.7.0 ## Bugfixes - hexyl can now be closed with `Ctrl-C` when reading input from STDIN, see #84 ## Changes - Breaking change (library): [`Printer::print_all`](https://docs.rs/hexyl/latest/hexyl/struct.Printer.html#method.print_all) does not take a second argument anymore. - Added an example on how to use `hexyl` as a library: https://github.com/sharkdp/hexyl/blob/v0.7.0/examples/simple.rs # v0.6.0 ## Features - `hexyl` can now be used as a library, see #67 (@tommilligan) - Added a new `-o`/`--display-offset` option to add a certain offset to the reported file positions, see #57 (@tommilligan) ## Bugfixes - Remove additional space on short input, see #69 (@nalshihabi) ## Other - Performance improvements, see #73 and #66 # v0.5.1 ## Bugfixes - A bug in the squeezing logic caused a wrong hexdump, see #62 (@awidegreen) - Some colors are printed even if they're disabled, see #64 (@awidegreen) - Fixed build failure on OpenBSD 6.5, see #61 # v0.5.0 ## Features - Added support for squeezing where reoccuring lines are squashed together and visualized with an asterisk. A new `-v`/`--no-squeezing` option can be used to disable the feature. For details, see #59 (@awidegreen) - Added a new `--border` option with support for various styles (Unicode, ASCII, None), see #54 (@dmke) - The `--length`/`-n` argument can be passed as a hexadecimal number (`hexyl -n 0xff /dev/urandom`), see #45 (@Qyriad) - Added `--bytes`/`-c` as an alias for `--length`/`-n`, see #48 (@selfup) ## Changes - Print header immediately before the first line, see #51 (@mziter) # v0.4.0 ## Features - Added a new `--color=always/auto/never` option which can be used to control `hexyl`s color output, see #30 (@bennetthardwick) - Use 16 colors instead of 256, see #38 ## Changes - Various speed improvements, see #33 (@kballard) ## Bugfixes - Proper Ctrl-C handling, see #35 - Proper handling of broken pipes (`hexyl … | head`) # v0.3.1 - Various (huge) performance improvements, see #23 and #24 (@kballard) - Replaced 24-bit truecolor ANSI codes by 8-bit codes to support more terminal emulators, fixes #9 # v0.3.0 Windows support # v0.2.0 Initial release hexyl-0.8.0/Cargo.lock0000644000000140641366716645300102710ustar00# This file is automatically @generated by Cargo. # It is not intended for manual editing. [[package]] name = "ansi_term" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "ansi_term" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "atty" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "hermit-abi 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "bitflags" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "clap" version = "2.33.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", "atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "term_size 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", "vec_map 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "hermit-abi" version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "hexyl" version = "0.8.0" dependencies = [ "ansi_term 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)", "atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.33.1 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "libc" version = "0.2.71" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "strsim" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "term_size" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "textwrap" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "term_size 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "unicode-width" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "vec_map" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "winapi" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" [metadata] "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" "checksum ansi_term 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" "checksum atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" "checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" "checksum clap 2.33.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bdfa80d47f954d53a35a64987ca1422f495b8d6483c0fe9f7117b36c2a792129" "checksum hermit-abi 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "91780f809e750b0a89f5544be56617ff6b1227ee485bcb06ebe10cdf89bd3b71" "checksum libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)" = "9457b06509d27052635f90d6466700c65095fdf75409b3fbdd903e988b886f49" "checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" "checksum term_size 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1e4129646ca0ed8f45d09b929036bafad5377103edd06e50bf574b353d2b08d9" "checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" "checksum unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" "checksum vec_map 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" "checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" hexyl-0.8.0/Cargo.toml0000644000000021201366716645300103020ustar00# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies # # If you believe there's an error in this file please file an # issue against the rust-lang/cargo repository. If you're # editing this file be aware that the upstream Cargo.toml # will likely look very different (and much more reasonable) [package] edition = "2018" name = "hexyl" version = "0.8.0" authors = ["David Peter "] description = "A command-line hex viewer" homepage = "https://github.com/sharkdp/hexyl" readme = "README.md" categories = ["command-line-utilities"] license = "MIT/Apache-2.0" repository = "https://github.com/sharkdp/hexyl" [profile.release] lto = true codegen-units = 1 [dependencies.ansi_term] version = "0.12" [dependencies.atty] version = "0.2" [dependencies.clap] version = "2" features = ["suggestions", "color", "wrap_help"] [dependencies.libc] version = "0.2" hexyl-0.8.0/Cargo.toml.orig010064400017500001735000000010151366716371600140000ustar0000000000000000[package] authors = ["David Peter "] categories = ["command-line-utilities"] description = "A command-line hex viewer" homepage = "https://github.com/sharkdp/hexyl" license = "MIT/Apache-2.0" name = "hexyl" readme = "README.md" repository = "https://github.com/sharkdp/hexyl" version = "0.8.0" edition = "2018" [dependencies] ansi_term = "0.12" atty = "0.2" libc = "0.2" [dependencies.clap] version = "2" features = ["suggestions", "color", "wrap_help"] [profile.release] lto = true codegen-units = 1 hexyl-0.8.0/LICENSE-APACHE010064400017500001735000000261351337011776200130360ustar0000000000000000 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. hexyl-0.8.0/LICENSE-MIT010064400017500001735000000017771337011776200125530ustar0000000000000000Permission 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. hexyl-0.8.0/README.md010064400017500001735000000047341366652377000124020ustar0000000000000000![](doc/logo.svg) [![Build Status](https://travis-ci.org/sharkdp/hexyl.svg?branch=master)](https://travis-ci.org/sharkdp/hexyl) [![](https://img.shields.io/crates/l/hexyl.svg?colorB=22ba4c)](https://crates.io/crates/hexyl) ![](https://img.shields.io/crates/v/hexyl.svg?colorB=00aa88) `hexyl` is a simple hex viewer for the terminal. It uses a colored output to distinguish different categories of bytes (NULL bytes, printable ASCII characters, ASCII whitespace characters, other ASCII characters and non-ASCII). ## Preview ![](https://i.imgur.com/MWO9uSL.png) ![](https://i.imgur.com/Dp7Wncz.png) ![](https://i.imgur.com/ln3TniI.png) ![](https://i.imgur.com/f8nm8g6.png) ## Installation ### On Ubuntu *... and other Debian-based Linux distributions.* If you run Ubuntu 19.10 (Eoan Ermine) or newer, you can install the [officially maintained package](https://packages.ubuntu.com/eoan/hexyl): ```bash sudo apt install hexyl ``` If you use an older version of Ubuntu, you can download the latest `.deb` package from the release page and install it via: ``` bash sudo dpkg -i hexyl_0.7.0_amd64.deb # adapt version number and architecture ``` ### On Debian If you run Debian Buster or newer, you can install the [officially maintained Debian package](https://packages.debian.org/buster/hexyl): ```bash sudo apt-get install hexyl ``` If you run an older version of Debian, see above for instructions on how to manually install `hexyl`. ### On Arch Linux You can install `hexyl` from [the official package repository](https://www.archlinux.org/packages/community/x86_64/hexyl/): ``` pacman -S hexyl ``` ### On Void Linux ``` xbps-install hexyl ``` ### On macOS ``` brew install hexyl ``` ### On FreeBSD ``` pkg install hexyl ``` ### Via Nix ``` nix-env -i hexyl ``` ### On other distributions Check out the [release page](https://github.com/sharkdp/hexyl/releases) for binary builds. ### On Windows For now, you will have to install from source via `cargo` (see below). Make sure that you use a terminal that supports ANSI escape sequences (like ConHost v2 since Windows 10 1703 or Windows Terminal since Windows 10 1903). ### Via cargo If you have Rust 1.36 or higher, you can install `hexyl` from source via `cargo`: ``` cargo install hexyl ``` ## License Licensed under either of * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) at your option. hexyl-0.8.0/ci/.gitattributes010064400017500001735000000000311337037362500143650ustar0000000000000000*.bash linguist-vendored hexyl-0.8.0/ci/before_deploy.bash010075500017500001735000000045751337037360300151670ustar0000000000000000#!/usr/bin/env bash # Building and packaging for release set -ex build() { cargo build --target "$TARGET" --release --verbose } pack() { local tempdir local out_dir local package_name tempdir=$(mktemp -d 2>/dev/null || mktemp -d -t tmp) out_dir=$(pwd) package_name="$PROJECT_NAME-$TRAVIS_TAG-$TARGET" # create a "staging" directory mkdir "$tempdir/$package_name" # copying the main binary cp "target/$TARGET/release/$PROJECT_NAME" "$tempdir/$package_name/" strip "$tempdir/$package_name/$PROJECT_NAME" # readme and license cp README.md "$tempdir/$package_name" cp LICENSE-MIT "$tempdir/$package_name" cp LICENSE-APACHE "$tempdir/$package_name" # archiving pushd "$tempdir" tar czf "$out_dir/$package_name.tar.gz" "$package_name"/* popd rm -r "$tempdir" } make_deb() { local tempdir local architecture local version local dpkgname local conflictname case $TARGET in x86_64*) architecture=amd64 ;; i686*) architecture=i386 ;; *) echo "ERROR: unknown target" >&2 return 1 ;; esac version=${TRAVIS_TAG#v} if [[ $TARGET = *musl* ]]; then dpkgname=$PROJECT_NAME-musl conflictname=$PROJECT_NAME else dpkgname=$PROJECT_NAME conflictname=$PROJECT_NAME-musl fi tempdir=$(mktemp -d 2>/dev/null || mktemp -d -t tmp) # copy the main binary install -Dm755 "target/$TARGET/release/$PROJECT_NAME" "$tempdir/usr/bin/$PROJECT_NAME" strip "$tempdir/usr/bin/$PROJECT_NAME" # readme and license install -Dm644 README.md "$tempdir/usr/share/doc/$PROJECT_NAME/README.md" install -Dm644 LICENSE-MIT "$tempdir/usr/share/doc/$PROJECT_NAME/LICENSE-MIT" install -Dm644 LICENSE-APACHE "$tempdir/usr/share/doc/$PROJECT_NAME/LICENSE-APACHE" # Control file mkdir "$tempdir/DEBIAN" cat > "$tempdir/DEBIAN/control" < Architecture: $architecture Provides: $PROJECT_NAME Conflicts: $conflictname Description: A command-line hex viewer. EOF fakeroot dpkg-deb --build "$tempdir" "${dpkgname}_${version}_${architecture}.deb" } main() { build pack if [[ $TARGET = *linux* ]]; then make_deb fi } main hexyl-0.8.0/doc/logo.svg010064400017500001735000000267651337110413700133420ustar0000000000000000 image/svg+xml hexyl-0.8.0/examples/simple.rs010064400017500001735000000011161363151506000145500ustar0000000000000000use std::io; use hexyl::{BorderStyle, Printer}; fn main() { let input = vec![ 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x44, 0x08, 0x02, 0x00, 0x00, 0x00, ]; let stdout = io::stdout(); let mut handle = stdout.lock(); let show_color = true; let use_squeezing = false; let border_style = BorderStyle::Unicode; let mut printer = Printer::new(&mut handle, show_color, border_style, use_squeezing); printer.print_all(&input[..]).unwrap(); } hexyl-0.8.0/src/bin/hexyl.rs010064400017500001735000000243401366716371600141550ustar0000000000000000#[macro_use] extern crate clap; use std::convert::TryFrom; use std::fs::File; use std::io::{self, prelude::*, SeekFrom}; use clap::{App, AppSettings, Arg}; use atty::Stream; use hexyl::{BorderStyle, Input, Printer}; fn run() -> Result<(), Box> { let app = App::new(crate_name!()) .setting(AppSettings::ColorAuto) .setting(AppSettings::ColoredHelp) .setting(AppSettings::DeriveDisplayOrder) .setting(AppSettings::UnifiedHelpMessage) .version(crate_version!()) .about(crate_description!()) .arg( Arg::with_name("FILE") .help("The file to display. If no FILE argument is given, read from STDIN."), ) .arg( Arg::with_name("length") .short("n") .long("length") .takes_value(true) .value_name("N") .help( "Only read N bytes from the input. The N argument can also include a \ unit with a decimal prefix (kB, MB, ..) or binary prefix (kiB, MiB, ..).\n\ Examples: --length=64, --length=4KiB", ), ) .arg( Arg::with_name("bytes") .short("c") .long("bytes") .takes_value(true) .value_name("N") .help("An alias for -n/--length"), ) .arg( Arg::with_name("skip") .short("s") .long("skip") .takes_value(true) .value_name("N") .help( "Skip the first N bytes of the input. The N argument can also include \ a unit (see `--length` for details)", ), ) .arg( Arg::with_name("block_size") .long("block-size") .takes_value(true) .value_name("SIZE") .help( "Sets the size of the `block` unit to SIZE.\n\ Examples: --block-size=1024, --block-size=4kB", ), ) .arg( Arg::with_name("nosqueezing") .short("v") .long("no-squeezing") .help( "Displays all input data. Otherwise any number of groups of output \ lines which would be identical to the preceding group of lines, are \ replaced with a line comprised of a single asterisk.", ), ) .arg( Arg::with_name("color") .long("color") .takes_value(true) .value_name("WHEN") .possible_values(&["always", "auto", "never"]) .default_value("always") .help( "When to use colors. The auto-mode only displays colors if the output \ goes to an interactive terminal", ), ) .arg( Arg::with_name("border") .long("border") .takes_value(true) .value_name("STYLE") .possible_values(&["unicode", "ascii", "none"]) .default_value("unicode") .help("Whether to draw a border with Unicode characters, ASCII characters, or none at all"), ) .arg( Arg::with_name("display_offset") .short("o") .long("display-offset") .takes_value(true) .value_name("N") .help("Add N bytes to the displayed file position. The N argument can also include \ a unit (see `--length` for details)"), ); let matches = app.get_matches_safe()?; let stdin = io::stdin(); let mut reader: Input = match matches.value_of("FILE") { Some(filename) => Input::File(File::open(filename)?), None => Input::Stdin(stdin.lock()), }; let block_size = matches .value_of("block_size") .and_then(|bs| bs.parse().ok().and_then(PositiveI64::new)) .unwrap_or_else(|| PositiveI64::new(512).unwrap()); let skip_arg = matches .value_of("skip") .and_then(|s| parse_byte_count(s, block_size)); if let Some(skip) = skip_arg { reader.seek(SeekFrom::Current(skip.into_inner()))?; } let length_arg = matches .value_of("length") .or_else(|| matches.value_of("bytes")); let mut reader = if let Some(length) = length_arg.and_then(|s| parse_byte_count(s, block_size)) { Box::new(reader.take(length.into())) } else { reader.into_inner() }; let show_color = match matches.value_of("color") { Some("never") => false, Some("auto") => atty::is(Stream::Stdout), _ => true, }; let border_style = match matches.value_of("border") { Some("unicode") => BorderStyle::Unicode, Some("ascii") => BorderStyle::Ascii, _ => BorderStyle::None, }; let squeeze = !matches.is_present("nosqueezing"); let display_offset = matches .value_of("display_offset") .and_then(|s| parse_byte_count(s, block_size)) .or(skip_arg) .unwrap_or_default() .into(); let stdout = io::stdout(); let mut stdout_lock = stdout.lock(); let mut printer = Printer::new(&mut stdout_lock, show_color, border_style, squeeze); printer.display_offset(display_offset); printer.print_all(&mut reader)?; Ok(()) } fn main() { // Enable ANSI support for Windows #[cfg(windows)] let _ = ansi_term::enable_ansi_support(); let result = run(); if let Err(err) = result { if let Some(clap_err) = err.downcast_ref::() { eprint!("{}", clap_err); // Clap errors already have newlines match clap_err.kind { // The exit code should not indicate an error for --help / --version clap::ErrorKind::HelpDisplayed | clap::ErrorKind::VersionDisplayed => { std::process::exit(0) } _ => (), } } else { eprintln!("Error: {}", err); } std::process::exit(1); } } #[derive(Clone, Copy, Debug, Default, Hash, Eq, Ord, PartialEq, PartialOrd)] pub struct PositiveI64(i64); impl PositiveI64 { pub fn new(x: i64) -> Option { if x.is_negative() { None } else { Some(Self(x)) } } pub fn into_inner(self) -> i64 { self.0 } } impl Into for PositiveI64 { fn into(self) -> u64 { u64::try_from(self.0) .expect("invariant broken: PositiveI64 should contain a positive i64 value") } } fn parse_byte_count(n: &str, block_size: PositiveI64) -> Option { const HEX_PREFIX: &str = "0x"; let n = { let mut chars = n.chars(); match chars.next()? { '+' => chars.as_str(), '-' => return None, _ => n, } }; if n.starts_with(HEX_PREFIX) { let n = &n[HEX_PREFIX.len()..]; match n.chars().next() { Some('+') | Some('-') => return None, _ => (), } return i64::from_str_radix(n, 16).ok().and_then(PositiveI64::new); } let (n, unit_multiplier) = match n.chars().position(|c| !c.is_ascii_digit()) { Some(unit_begin_idx) => { let (n, raw_unit) = n.split_at(unit_begin_idx); let raw_unit = raw_unit.to_lowercase(); ( n, [ ("b", 1), ("kb", 1000i64.pow(1)), ("mb", 1000i64.pow(2)), ("gb", 1000i64.pow(3)), ("tb", 1000i64.pow(4)), ("kib", 1024i64.pow(1)), ("mib", 1024i64.pow(2)), ("gib", 1024i64.pow(3)), ("tib", 1024i64.pow(4)), ("block", block_size.into_inner()), ] .iter() .cloned() .find_map(|(unit, multiplier)| { if unit == raw_unit { Some(multiplier) } else { None } })?, ) } None => (n, 1), }; n.parse::() .ok()? .checked_mul(unit_multiplier) .and_then(PositiveI64::new) } #[test] fn test_parse_byte_count() { macro_rules! success { ($input: expr, $expected: expr) => { success!($input, 512, $expected) }; ($input: expr, $block_size: expr, $expected: expr) => { assert_eq!( parse_byte_count($input, PositiveI64::new($block_size).unwrap()), Some(PositiveI64::new($expected).unwrap()) ); }; } macro_rules! error { ($input: expr) => { assert_eq!( parse_byte_count($input, PositiveI64::new(512).unwrap()), None ); }; } success!("0", 0); success!("1", 1); success!("1", 1); success!("100", 100); success!("+100", 100); success!("1KB", 1000); success!("2MB", 2000000); success!("3GB", 3000000000); success!("4TB", 4000000000000); success!("+4TB", 4000000000000); success!("1GiB", 1073741824); success!("2TiB", 2199023255552); success!("+2TiB", 2199023255552); success!("0xff", 255); success!("0xEE", 238); success!("+0xFF", 255); success!("1block", 512, 512); success!("2block", 512, 1024); success!("1block", 4, 4); success!("2block", 4, 8); // empty string is invalid error!(""); // These are also bad. error!("+"); error!("-"); error!("K"); error!("k"); error!("m"); error!("block"); // leading/trailing space is invalid error!(" 0"); error!("0 "); // Negatives make no sense for byte counts error!("-1"); error!("0x-12"); // This was previously accepted but shouldn't be. error!("0x+12"); // invalid suffix error!("1234asdf"); // bad numbers error!("asdf1234"); error!("a1s2d3f4"); // multiplication overflows u64 error!("20000000TiB"); } hexyl-0.8.0/src/input.rs010064400017500001735000000036561366712722000134100ustar0000000000000000use std::convert::TryFrom; use std::fs; use std::io::{self, copy, sink, Read, Seek, SeekFrom}; pub enum Input<'a> { File(fs::File), Stdin(io::StdinLock<'a>), } impl<'a> Read for Input<'a> { fn read(&mut self, buf: &mut [u8]) -> io::Result { match *self { Input::File(ref mut file) => file.read(buf), Input::Stdin(ref mut stdin) => stdin.read(buf), } } } impl<'a> Seek for Input<'a> { fn seek(&mut self, pos: SeekFrom) -> io::Result { fn try_skip(reader: R, pos: SeekFrom, err_desc: &'static str) -> io::Result where R: Read, { let cant_seek_abs_err = || Err(io::Error::new(io::ErrorKind::Other, err_desc)); let offset = match pos { SeekFrom::Current(o) => u64::try_from(o).or_else(|_e| cant_seek_abs_err())?, SeekFrom::Start(_) | SeekFrom::End(_) => cant_seek_abs_err()?, }; copy(&mut reader.take(offset), &mut sink()) } match *self { Input::File(ref mut file) => { let seek_res = file.seek(pos); if let Err(Some(libc::ESPIPE)) = seek_res.as_ref().map_err(|err| err.raw_os_error()) { try_skip( file, pos, "Pipes only support seeking forward with a relative offset", ) } else { seek_res } } Input::Stdin(ref mut stdin) => try_skip( stdin, pos, "STDIN only supports seeking forward with a relative offset", ), } } } impl<'a> Input<'a> { pub fn into_inner(self) -> Box { match self { Input::File(file) => Box::new(file), Input::Stdin(stdin) => Box::new(stdin), } } } hexyl-0.8.0/src/lib.rs010064400017500001735000000361751366716534200130300ustar0000000000000000pub(crate) mod input; pub mod squeezer; pub use input::*; use std::io::{self, Read, Write}; use ansi_term::Color; use ansi_term::Color::Fixed; use crate::squeezer::{SqueezeAction, Squeezer}; const BUFFER_SIZE: usize = 256; const COLOR_NULL: Color = Fixed(242); // grey const COLOR_OFFSET: Color = Fixed(242); // grey const COLOR_ASCII_PRINTABLE: Color = Color::Cyan; const COLOR_ASCII_WHITESPACE: Color = Color::Green; const COLOR_ASCII_OTHER: Color = Color::Purple; const COLOR_NONASCII: Color = Color::Yellow; pub enum ByteCategory { Null, AsciiPrintable, AsciiWhitespace, AsciiOther, NonAscii, } #[derive(Copy, Clone)] struct Byte(u8); impl Byte { fn category(self) -> ByteCategory { if self.0 == 0x00 { ByteCategory::Null } else if self.0.is_ascii_graphic() { ByteCategory::AsciiPrintable } else if self.0.is_ascii_whitespace() { ByteCategory::AsciiWhitespace } else if self.0.is_ascii() { ByteCategory::AsciiOther } else { ByteCategory::NonAscii } } fn color(self) -> &'static Color { use crate::ByteCategory::*; match self.category() { Null => &COLOR_NULL, AsciiPrintable => &COLOR_ASCII_PRINTABLE, AsciiWhitespace => &COLOR_ASCII_WHITESPACE, AsciiOther => &COLOR_ASCII_OTHER, NonAscii => &COLOR_NONASCII, } } fn as_char(self) -> char { use crate::ByteCategory::*; match self.category() { Null => '0', AsciiPrintable => self.0 as char, AsciiWhitespace if self.0 == 0x20 => ' ', AsciiWhitespace => '_', AsciiOther => '•', NonAscii => '×', } } } struct BorderElements { left_corner: char, horizontal_line: char, column_separator: char, right_corner: char, } pub enum BorderStyle { Unicode, Ascii, None, } impl BorderStyle { fn header_elems(&self) -> Option { match self { BorderStyle::Unicode => Some(BorderElements { left_corner: '┌', horizontal_line: '─', column_separator: '┬', right_corner: '┐', }), BorderStyle::Ascii => Some(BorderElements { left_corner: '+', horizontal_line: '-', column_separator: '+', right_corner: '+', }), BorderStyle::None => None, } } fn footer_elems(&self) -> Option { match self { BorderStyle::Unicode => Some(BorderElements { left_corner: '└', horizontal_line: '─', column_separator: '┴', right_corner: '┘', }), BorderStyle::Ascii => Some(BorderElements { left_corner: '+', horizontal_line: '-', column_separator: '+', right_corner: '+', }), BorderStyle::None => None, } } fn outer_sep(&self) -> char { match self { BorderStyle::Unicode => '│', BorderStyle::Ascii => '|', BorderStyle::None => ' ', } } fn inner_sep(&self) -> char { match self { BorderStyle::Unicode => '┊', BorderStyle::Ascii => '|', BorderStyle::None => ' ', } } } pub struct Printer<'a, Writer: Write> { idx: u64, /// The raw bytes used as input for the current line. raw_line: Vec, /// The buffered line built with each byte, ready to print to writer. buffer_line: Vec, writer: &'a mut Writer, show_color: bool, border_style: BorderStyle, header_was_printed: bool, byte_hex_table: Vec, byte_char_table: Vec, squeezer: Squeezer, display_offset: u64, } impl<'a, Writer: Write> Printer<'a, Writer> { pub fn new( writer: &'a mut Writer, show_color: bool, border_style: BorderStyle, use_squeeze: bool, ) -> Printer<'a, Writer> { Printer { idx: 1, raw_line: vec![], buffer_line: vec![], writer, show_color, border_style, header_was_printed: false, byte_hex_table: (0u8..=u8::max_value()) .map(|i| { let byte_hex = format!("{:02x} ", i); if show_color { Byte(i).color().paint(byte_hex).to_string() } else { byte_hex } }) .collect(), byte_char_table: (0u8..=u8::max_value()) .map(|i| { let byte_char = format!("{}", Byte(i).as_char()); if show_color { Byte(i).color().paint(byte_char).to_string() } else { byte_char } }) .collect(), squeezer: Squeezer::new(use_squeeze), display_offset: 0, } } pub fn display_offset(&mut self, display_offset: u64) -> &mut Self { self.display_offset = display_offset; self } pub fn header(&mut self) { if let Some(border_elements) = self.border_style.header_elems() { let h = border_elements.horizontal_line; let h8 = h.to_string().repeat(8); let h25 = h.to_string().repeat(25); writeln!( self.writer, "{l}{h8}{c}{h25}{c}{h25}{c}{h8}{c}{h8}{r}", l = border_elements.left_corner, c = border_elements.column_separator, r = border_elements.right_corner, h8 = h8, h25 = h25 ) .ok(); } } pub fn footer(&mut self) { if let Some(border_elements) = self.border_style.footer_elems() { let h = border_elements.horizontal_line; let h8 = h.to_string().repeat(8); let h25 = h.to_string().repeat(25); writeln!( self.writer, "{l}{h8}{c}{h25}{c}{h25}{c}{h8}{c}{h8}{r}", l = border_elements.left_corner, c = border_elements.column_separator, r = border_elements.right_corner, h8 = h8, h25 = h25 ) .ok(); } } fn print_position_indicator(&mut self) { if !self.header_was_printed { self.header(); self.header_was_printed = true; } let style = COLOR_OFFSET.normal(); let byte_index = format!("{:08x}", self.idx - 1 + self.display_offset); let formatted_string = if self.show_color { format!("{}", style.paint(byte_index)) } else { byte_index }; let _ = write!( &mut self.buffer_line, "{}{}{} ", self.border_style.outer_sep(), formatted_string, self.border_style.outer_sep() ); } pub fn print_byte(&mut self, b: u8) -> io::Result<()> { if self.idx % 16 == 1 { self.print_position_indicator(); } write!(&mut self.buffer_line, "{}", self.byte_hex_table[b as usize])?; self.raw_line.push(b); self.squeezer.process(b, self.idx); match self.idx % 16 { 8 => { let _ = write!(&mut self.buffer_line, "{} ", self.border_style.inner_sep()); } 0 => { self.print_textline()?; } _ => {} } self.idx += 1; Ok(()) } pub fn print_textline(&mut self) -> io::Result<()> { let len = self.raw_line.len(); if len == 0 { if self.squeezer.active() { self.print_position_indicator(); let _ = writeln!( &mut self.buffer_line, "{0:1$}{4}{0:2$}{5}{0:3$}{4}{0:3$}{5}", "", 24, 25, 8, self.border_style.inner_sep(), self.border_style.outer_sep(), ); self.writer.write_all(&self.buffer_line)?; } return Ok(()); } let squeeze_action = self.squeezer.action(); if squeeze_action != SqueezeAction::Delete { if len < 8 { let _ = write!( &mut self.buffer_line, "{0:1$}{3}{0:2$}{4}", "", 3 * (8 - len), 1 + 3 * 8, self.border_style.inner_sep(), self.border_style.outer_sep(), ); } else { let _ = write!( &mut self.buffer_line, "{0:1$}{2}", "", 3 * (16 - len), self.border_style.outer_sep() ); } let mut idx = 1; for &b in self.raw_line.iter() { let _ = write!( &mut self.buffer_line, "{}", self.byte_char_table[b as usize] ); if idx == 8 { let _ = write!(&mut self.buffer_line, "{}", self.border_style.inner_sep()); } idx += 1; } if len < 8 { let _ = writeln!( &mut self.buffer_line, "{0:1$}{3}{0:2$}{4}", "", 8 - len, 8, self.border_style.inner_sep(), self.border_style.outer_sep(), ); } else { let _ = writeln!( &mut self.buffer_line, "{0:1$}{2}", "", 16 - len, self.border_style.outer_sep() ); } } match squeeze_action { SqueezeAction::Print => { self.buffer_line.clear(); let style = COLOR_OFFSET.normal(); let asterisk = if self.show_color { format!("{}", style.paint("*")) } else { String::from("*") }; let _ = writeln!( &mut self.buffer_line, "{5}{0}{1:2$}{5}{1:3$}{6}{1:3$}{5}{1:4$}{6}{1:4$}{5}", asterisk, "", 7, 25, 8, self.border_style.outer_sep(), self.border_style.inner_sep(), ); } SqueezeAction::Delete => self.buffer_line.clear(), SqueezeAction::Ignore => (), } self.writer.write_all(&self.buffer_line)?; self.raw_line.clear(); self.buffer_line.clear(); self.squeezer.advance(); Ok(()) } pub fn header_was_printed(&self) -> bool { self.header_was_printed } /// Loop through the given `Reader`, printing until the `Reader` buffer /// is exhausted. pub fn print_all( &mut self, mut reader: Reader, ) -> Result<(), Box> { let mut buffer = [0; BUFFER_SIZE]; 'mainloop: loop { let size = reader.read(&mut buffer)?; if size == 0 { break; } for b in &buffer[..size] { let res = self.print_byte(*b); if res.is_err() { // Broken pipe break 'mainloop; } } } // Finish last line self.print_textline().ok(); if !self.header_was_printed() { self.header(); } self.footer(); Ok(()) } } #[cfg(test)] mod tests { use std::io; use std::str; use super::*; fn assert_print_all_output(input: Reader, expected_string: String) -> () { let mut output = vec![]; let mut printer = Printer::new(&mut output, false, BorderStyle::Unicode, true); printer.print_all(input).unwrap(); let actual_string: &str = str::from_utf8(&output).unwrap(); assert_eq!(actual_string, expected_string) } #[test] fn empty_file_passes() { let input = io::empty(); let expected_string = "\ ┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐ └────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘ " .to_owned(); assert_print_all_output(input, expected_string); } #[test] fn short_input_passes() { let input = io::Cursor::new(b"spam"); let expected_string = "\ ┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐ │00000000│ 73 70 61 6d ┊ │spam ┊ │ └────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘ " .to_owned(); assert_print_all_output(input, expected_string); } #[test] fn display_offset() { let input = io::Cursor::new(b"spamspamspamspamspam"); let expected_string = "\ ┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐ │deadbeef│ 73 70 61 6d 73 70 61 6d ┊ 73 70 61 6d 73 70 61 6d │spamspam┊spamspam│ │deadbeff│ 73 70 61 6d ┊ │spam ┊ │ └────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘ " .to_owned(); let mut output = vec![]; let mut printer: Printer> = Printer::new(&mut output, false, BorderStyle::Unicode, true); printer.display_offset(0xdeadbeef); printer.print_all(input).unwrap(); let actual_string: &str = str::from_utf8(&output).unwrap(); assert_eq!(actual_string, expected_string) } } hexyl-0.8.0/src/squeezer.rs010064400017500001735000000272471366712722000141160ustar0000000000000000#[derive(Debug, PartialEq)] enum SqueezeState { /// not enabled Disabled, /// Will be set from all states if equal condition can't be hold up. /// Set if previous byte is not equal the current processed byte. NoSqueeze, /// Valid for a whole line to identify if it is candidate for squeezing Probe, /// Squeeze line parsing is active, but EOL is not reached yet SqueezeActive, /// Squeeze line, EOL is reached, will influence the action Squeeze, /// same as Squeeze, however this is only for the first line after /// the squeeze candidate has been set. SqueezeFirstLine, /// same as SqueezeActive, however this is only for the first line after /// the squeeze candidate has been set. SqueezeActiveFirstLine, } pub struct Squeezer { state: SqueezeState, byte: u8, } #[derive(Debug, PartialEq)] pub enum SqueezeAction { Ignore, Print, Delete, } /// line size const LSIZE: u64 = 16; impl Squeezer { pub fn new(enabled: bool) -> Squeezer { Squeezer { state: if enabled { SqueezeState::Probe } else { SqueezeState::Disabled }, byte: 0, } } pub fn process(&mut self, b: u8, i: u64) { use self::SqueezeState::*; if self.state == Disabled { return; } let eq = b == self.byte; if i % LSIZE == 0 { if !eq { self.state = Probe; } else { self.state = match self.state { NoSqueeze => Probe, Probe => SqueezeActiveFirstLine, SqueezeActiveFirstLine => SqueezeFirstLine, SqueezeFirstLine => SqueezeActive, SqueezeActive => Squeeze, Squeeze => SqueezeActive, Disabled => Disabled, }; } } else if !eq { if i % LSIZE == 1 { self.state = Probe; } else if i % LSIZE != 1 { self.state = NoSqueeze; } } self.byte = b; } pub fn active(&self) -> bool { use self::SqueezeState::*; match self.state { Squeeze | SqueezeActive | SqueezeFirstLine | SqueezeActiveFirstLine => true, _ => false, } } pub fn action(&self) -> SqueezeAction { match self.state { SqueezeState::SqueezeFirstLine => SqueezeAction::Print, SqueezeState::Squeeze => SqueezeAction::Delete, _ => SqueezeAction::Ignore, } } pub fn advance(&mut self) { match self.state { SqueezeState::SqueezeFirstLine => { self.state = SqueezeState::SqueezeActive; } SqueezeState::Squeeze => { self.state = SqueezeState::SqueezeActive; } _ => {} } } } #[cfg(test)] mod tests { use super::*; const LSIZE_USIZE: usize = LSIZE as usize; #[test] fn three_same_lines() { const LINES: usize = 3; let v = vec![0u8; LINES * LSIZE_USIZE]; let mut s = Squeezer::new(true); // just initialized assert_eq!(s.action(), SqueezeAction::Ignore); s.advance(); let exp = vec![ SqueezeAction::Ignore, // first line, print as is SqueezeAction::Print, // print squeeze symbol SqueezeAction::Delete, // delete reoccurring line ]; let mut line = 0; let mut idx = 1; for z in v.chunks(LSIZE_USIZE) { for i in z { s.process(*i, idx); idx += 1; } let action = s.action(); s.advance(); assert_eq!(action, exp[line]); line += 1; } } #[test] fn incomplete_while_squeeze() { // fourth line only has 12 bytes and should be printed let v = vec![0u8; 3 * LSIZE_USIZE + 12]; let mut s = Squeezer::new(true); // just initialized assert_eq!(s.action(), SqueezeAction::Ignore); s.advance(); let exp = vec![ SqueezeAction::Ignore, // first line, print as is SqueezeAction::Print, // print squeeze symbol SqueezeAction::Delete, // delete reoccurring line SqueezeAction::Ignore, // last line only 12 bytes, print it ]; let mut line = 0; let mut idx = 1; for z in v.chunks(LSIZE_USIZE) { for i in z { s.process(*i, idx); idx += 1; } assert_eq!(s.action(), exp[line]); s.advance(); line += 1; } } #[test] /// all three lines are different, print all fn three_different_lines() { let mut v: Vec = vec![]; v.extend(vec![0u8; 16]); v.extend(vec![1u8; 16]); v.extend(vec![2u8; 16]); let mut s = Squeezer::new(true); // just initialized assert_eq!(s.action(), SqueezeAction::Ignore); s.advance(); let exp = vec![ SqueezeAction::Ignore, // first line, print as is SqueezeAction::Ignore, // different SqueezeAction::Ignore, // different ]; let mut line = 0; let mut idx = 1; for z in v.chunks(LSIZE_USIZE) { for i in z { s.process(*i, idx); idx += 1; } let action = s.action(); assert_eq!(action, exp[line]); s.advance(); line += 1; } } #[test] /// first two lines same, hence squeeze symbol, third line diff, hence /// print fn one_squeeze_no_delete() { const LINES: usize = 3; let mut v = vec![0u8; (LINES - 1) * LSIZE_USIZE]; v.extend(vec![1u8; 16]); let mut s = Squeezer::new(true); // just initialized assert_eq!(s.action(), SqueezeAction::Ignore); s.advance(); let exp = vec![ SqueezeAction::Ignore, // first line, print as is SqueezeAction::Print, // print squeeze symbol SqueezeAction::Ignore, // different lines, print again ]; let mut line = 0; let mut idx = 1; for z in v.chunks(LSIZE_USIZE) { for i in z { s.process(*i, idx); idx += 1; } let action = s.action(); s.advance(); assert_eq!(action, exp[line]); line += 1; } } #[test] /// First line all eq, 2nd half eq with first line, then change fn second_line_different() { const LINES: usize = 2; let mut v = vec![0u8; (LINES - 1) * LSIZE_USIZE]; v.extend(vec![0u8; 8]); v.extend(vec![1u8; 8]); let mut s = Squeezer::new(true); // just initialized assert_eq!(s.action(), SqueezeAction::Ignore); s.advance(); let exp = vec![ SqueezeAction::Ignore, // first line, print as is SqueezeAction::Ignore, // print squeeze symbol ]; let mut line = 0; let mut idx = 1; for z in v.chunks(LSIZE_USIZE) { for i in z { s.process(*i, idx); idx += 1; } let action = s.action(); s.advance(); assert_eq!(action, exp[line]); line += 1; } } #[test] /// all three lines never become squeeze candidate (diff within line) fn never_squeeze_candidate() { let mut v = vec![]; v.extend(vec![0u8; 8]); v.extend(vec![1u8; 8]); v.extend(vec![0u8; 8]); v.extend(vec![1u8; 8]); v.extend(vec![0u8; 8]); v.extend(vec![1u8; 8]); let mut s = Squeezer::new(true); // just initialized assert_eq!(s.action(), SqueezeAction::Ignore); s.advance(); let exp = vec![ SqueezeAction::Ignore, // first line, print as is SqueezeAction::Ignore, // print squeeze symbol SqueezeAction::Ignore, // print squeeze symbol ]; let mut line = 0; let mut idx = 1; for z in v.chunks(LSIZE_USIZE) { for i in z { s.process(*i, idx); idx += 1; } let action = s.action(); s.advance(); assert_eq!(action, exp[line]); line += 1; } } #[test] fn mix_everything() { let mut v = vec![]; v.extend(vec![10u8; 16]); // print v.extend(vec![20u8; 16]); // print v.extend(vec![0u8; 16]); // print v.extend(vec![0u8; 16]); // * v.extend(vec![10u8; 16]); // print v.extend(vec![20u8; 16]); // print v.extend(vec![0u8; 16]); // print v.extend(vec![0u8; 16]); // * v.extend(vec![0u8; 16]); // delete v.extend(vec![0u8; 16]); // delete* v.extend(vec![20u8; 16]); // print v.extend(vec![0u8; 12]); // print, only 12 bytes let mut s = Squeezer::new(true); // just initialized assert_eq!(s.action(), SqueezeAction::Ignore); s.advance(); let exp = vec![ SqueezeAction::Ignore, SqueezeAction::Ignore, SqueezeAction::Ignore, SqueezeAction::Print, SqueezeAction::Ignore, SqueezeAction::Ignore, SqueezeAction::Ignore, SqueezeAction::Print, SqueezeAction::Delete, SqueezeAction::Delete, SqueezeAction::Ignore, SqueezeAction::Ignore, ]; let mut line = 0; let mut idx = 1; for z in v.chunks(LSIZE_USIZE) { for i in z { s.process(*i, idx); idx += 1; } let action = s.action(); s.advance(); assert_eq!(action, exp[line]); line += 1; } } #[test] fn last_char_diff() { // see issue #62 let mut v = vec![]; v.extend(vec![20u8; 16]); v.extend(vec![20u8; 15]); v.push(61); v.extend(vec![20u8; 16]); v.extend(vec![20u8; 16]); let mut s = Squeezer::new(true); // just initialized assert_eq!(s.action(), SqueezeAction::Ignore); s.advance(); let exp = vec![ SqueezeAction::Ignore, // print as is SqueezeAction::Ignore, // print as is SqueezeAction::Ignore, // print as is SqueezeAction::Print, // print '*' char ]; let mut line = 0; let mut idx = 1; for z in v.chunks(LSIZE_USIZE) { for i in z { s.process(*i, idx); idx += 1; } assert_eq!(s.action(), exp[line]); s.advance(); line += 1; } } #[test] fn first_char_diff() { // see issue #62 let mut v = vec![]; v.extend(vec![20u8; 16]); v.push(61); v.extend(vec![20u8; 15]); v.extend(vec![20u8; 16]); let mut s = Squeezer::new(true); // just initialized assert_eq!(s.action(), SqueezeAction::Ignore); s.advance(); let exp = vec![ SqueezeAction::Ignore, // print as is SqueezeAction::Ignore, // print as is SqueezeAction::Ignore, // print as is ]; let mut line = 0; let mut idx = 1; for z in v.chunks(LSIZE_USIZE) { for i in z { s.process(*i, idx); idx += 1; } assert_eq!(s.action(), exp[line]); s.advance(); line += 1; } } }