├── .gitignore ├── .github └── ss.png ├── CONTRIBUTING.md ├── README.md ├── Cargo.toml ├── LICENSE ├── Cargo.lock └── src └── main.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | -------------------------------------------------------------------------------- /.github/ss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uetchy/binyl/HEAD/.github/ss.png -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribution Guide 2 | 3 | ## Publish (Maintainers-only) 4 | 5 | ```bash 6 | cargo set-version 7 | git commit -am 'chore: bump version' 8 | cargo publish 9 | ``` 10 | 11 | ## References 12 | 13 | - [Command Line Applications in Rust](https://rust-cli.github.io/book/) 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # binyl 2 | 3 | A bitwise UTF-8 string inspection tool written in Rust. 4 | 5 | ![Screenshot](https://github.com/uetchy/binyl/blob/master/.github/ss.png?raw=true) 6 | 7 | ## Installation 8 | 9 | ```bash 10 | cargo install binyl 11 | ``` 12 | 13 | ## Usage 14 | 15 | ```bash 16 | binyl 17 | 18 | # or 19 | 20 | echo わたしはネコ🐈 | binyl 21 | cat some.file | binyl 22 | ``` 23 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "binyl" 3 | description = "A bitwise UTF-8 string inspection tool" 4 | version = "1.0.0" 5 | authors = ["Yasuaki Uechi "] 6 | edition = "2021" 7 | license-file = "LICENSE" 8 | repository = "https://github.com/uetchy/binyl" 9 | 10 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 11 | 12 | [dependencies] 13 | anyhow = "1.0.51" 14 | colored = "2.0.0" 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2020 Yasuaki Uechi 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "anyhow" 7 | version = "1.0.51" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8b26702f315f53b6071259e15dd9d64528213b44d61de1ec926eca7715d62203" 10 | 11 | [[package]] 12 | name = "atty" 13 | version = "0.2.14" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 16 | dependencies = [ 17 | "hermit-abi", 18 | "libc", 19 | "winapi", 20 | ] 21 | 22 | [[package]] 23 | name = "binyl" 24 | version = "1.0.0" 25 | dependencies = [ 26 | "anyhow", 27 | "colored", 28 | ] 29 | 30 | [[package]] 31 | name = "colored" 32 | version = "2.0.0" 33 | source = "registry+https://github.com/rust-lang/crates.io-index" 34 | checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd" 35 | dependencies = [ 36 | "atty", 37 | "lazy_static", 38 | "winapi", 39 | ] 40 | 41 | [[package]] 42 | name = "hermit-abi" 43 | version = "0.1.11" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "8a0d737e0f947a1864e93d33fdef4af8445a00d1ed8dc0c8ddb73139ea6abf15" 46 | dependencies = [ 47 | "libc", 48 | ] 49 | 50 | [[package]] 51 | name = "lazy_static" 52 | version = "1.4.0" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 55 | 56 | [[package]] 57 | name = "libc" 58 | version = "0.2.69" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | checksum = "99e85c08494b21a9054e7fe1374a732aeadaff3980b6990b94bfd3a70f690005" 61 | 62 | [[package]] 63 | name = "winapi" 64 | version = "0.3.8" 65 | source = "registry+https://github.com/rust-lang/crates.io-index" 66 | checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" 67 | dependencies = [ 68 | "winapi-i686-pc-windows-gnu", 69 | "winapi-x86_64-pc-windows-gnu", 70 | ] 71 | 72 | [[package]] 73 | name = "winapi-i686-pc-windows-gnu" 74 | version = "0.4.0" 75 | source = "registry+https://github.com/rust-lang/crates.io-index" 76 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 77 | 78 | [[package]] 79 | name = "winapi-x86_64-pc-windows-gnu" 80 | version = "0.4.0" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 83 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{anyhow, Context, Result}; 2 | use colored::*; 3 | use std::env; 4 | 5 | fn main() -> Result<()> { 6 | let args: Vec = env::args().collect(); 7 | let content = match args.get(1) { 8 | // read from file 9 | Some(filename) => std::fs::read_to_string(&filename) 10 | .with_context(|| format!("could not read file `{}`", filename))?, 11 | 12 | // read from stdin 13 | None => { 14 | let mut line = String::new(); 15 | std::io::stdin().read_line(&mut line)?; 16 | line 17 | } 18 | }; 19 | 20 | parse_string(&content) 21 | } 22 | 23 | fn parse_string(content: &str) -> Result<()> { 24 | let mut bytes = content.bytes(); 25 | 26 | while let Some(b) = bytes.next() { 27 | if b & 0x80 == 0 { 28 | // if MSB is 0, is is just ASCII. 29 | println!( 30 | "{} {} {} (U+{:04X} ASCII={})", 31 | "├".bright_black(), 32 | format!("{:08b}", b).cyan(), 33 | std::str::from_utf8(&[b])? 34 | .replace("\x0a", "↵") 35 | .replace("\x20", "") 36 | .bold(), 37 | b, 38 | b, 39 | ); 40 | } else { 41 | let nb = (!b).leading_zeros(); 42 | let (first, last) = split_octet(b, nb); 43 | 44 | print!("{} ", "├".bright_black()); 45 | print!( 46 | "{}{}", 47 | format!("{:04b}", first).magenta(), 48 | format!("{:04b}", last).green(), 49 | ); 50 | 51 | let mut v: Vec = vec![]; 52 | let mut f: u32 = 0; 53 | let base = 6 * (nb - 1); 54 | 55 | v.push(b); 56 | 57 | f |= (b as u32 & (u32::pow(2, 7 - nb) - 1)) << base; 58 | 59 | for i in 0..(nb - 1) { 60 | let next_byte = bytes.next().unwrap(); 61 | 62 | // if first 2 MSB are not '10', it's illegal sequence. 63 | if next_byte & 0xC0 != 0x80 { 64 | return Err(anyhow!("Illegal byte")); 65 | } 66 | 67 | let (first, last) = split_octet(next_byte, 2); 68 | 69 | print!( 70 | "{}{}", 71 | format!("{:02b}", first).bright_black(), 72 | format!("{:06b}", last).green() 73 | ); 74 | 75 | f |= (next_byte as u32 & 0x3f) << base - (i + 1) * 6; 76 | v.push(next_byte); 77 | } 78 | 79 | println!( 80 | " ({} bytes) = {} {} (U+{:04X})", 81 | nb, 82 | format!("{:0b}", f).cyan(), 83 | std::str::from_utf8(&v[..]) 84 | .unwrap_or("[INVALID CODE]") 85 | .bold(), 86 | f 87 | ); 88 | } 89 | } 90 | 91 | Ok(()) 92 | } 93 | 94 | fn split_octet(octet: u8, separate_at: u32) -> (u8, u8) { 95 | let mask = u8::pow(2, separate_at) - 1; 96 | let first = (octet & !mask) >> (8 - separate_at); 97 | let last = octet & mask; 98 | (first, last) 99 | } 100 | --------------------------------------------------------------------------------