cli dollcode encoder and decoder

init

0xda157 44e35950

+439
+1
.gitignore
··· 1 + /target
+132
Cargo.lock
··· 1 + # This file is automatically @generated by Cargo. 2 + # It is not intended for manual editing. 3 + version = 4 4 + 5 + [[package]] 6 + name = "anstream" 7 + version = "0.6.21" 8 + source = "registry+https://github.com/rust-lang/crates.io-index" 9 + checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" 10 + dependencies = [ 11 + "anstyle", 12 + "anstyle-parse", 13 + "anstyle-query", 14 + "anstyle-wincon", 15 + "colorchoice", 16 + "is_terminal_polyfill", 17 + "utf8parse", 18 + ] 19 + 20 + [[package]] 21 + name = "anstyle" 22 + version = "1.0.13" 23 + source = "registry+https://github.com/rust-lang/crates.io-index" 24 + checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" 25 + 26 + [[package]] 27 + name = "anstyle-parse" 28 + version = "0.2.7" 29 + source = "registry+https://github.com/rust-lang/crates.io-index" 30 + checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" 31 + dependencies = [ 32 + "utf8parse", 33 + ] 34 + 35 + [[package]] 36 + name = "anstyle-query" 37 + version = "1.1.5" 38 + source = "registry+https://github.com/rust-lang/crates.io-index" 39 + checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" 40 + dependencies = [ 41 + "windows-sys", 42 + ] 43 + 44 + [[package]] 45 + name = "anstyle-wincon" 46 + version = "3.0.11" 47 + source = "registry+https://github.com/rust-lang/crates.io-index" 48 + checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" 49 + dependencies = [ 50 + "anstyle", 51 + "once_cell_polyfill", 52 + "windows-sys", 53 + ] 54 + 55 + [[package]] 56 + name = "clap" 57 + version = "4.5.51" 58 + source = "registry+https://github.com/rust-lang/crates.io-index" 59 + checksum = "4c26d721170e0295f191a69bd9a1f93efcdb0aff38684b61ab5750468972e5f5" 60 + dependencies = [ 61 + "clap_builder", 62 + ] 63 + 64 + [[package]] 65 + name = "clap_builder" 66 + version = "4.5.51" 67 + source = "registry+https://github.com/rust-lang/crates.io-index" 68 + checksum = "75835f0c7bf681bfd05abe44e965760fea999a5286c6eb2d59883634fd02011a" 69 + dependencies = [ 70 + "anstream", 71 + "anstyle", 72 + "clap_lex", 73 + "strsim", 74 + ] 75 + 76 + [[package]] 77 + name = "clap_lex" 78 + version = "0.7.6" 79 + source = "registry+https://github.com/rust-lang/crates.io-index" 80 + checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" 81 + 82 + [[package]] 83 + name = "colorchoice" 84 + version = "1.0.4" 85 + source = "registry+https://github.com/rust-lang/crates.io-index" 86 + checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" 87 + 88 + [[package]] 89 + name = "is_terminal_polyfill" 90 + version = "1.70.2" 91 + source = "registry+https://github.com/rust-lang/crates.io-index" 92 + checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" 93 + 94 + [[package]] 95 + name = "once_cell_polyfill" 96 + version = "1.70.2" 97 + source = "registry+https://github.com/rust-lang/crates.io-index" 98 + checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" 99 + 100 + [[package]] 101 + name = "porcelain" 102 + version = "0.1.0" 103 + dependencies = [ 104 + "clap", 105 + ] 106 + 107 + [[package]] 108 + name = "strsim" 109 + version = "0.11.1" 110 + source = "registry+https://github.com/rust-lang/crates.io-index" 111 + checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 112 + 113 + [[package]] 114 + name = "utf8parse" 115 + version = "0.2.2" 116 + source = "registry+https://github.com/rust-lang/crates.io-index" 117 + checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 118 + 119 + [[package]] 120 + name = "windows-link" 121 + version = "0.2.1" 122 + source = "registry+https://github.com/rust-lang/crates.io-index" 123 + checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" 124 + 125 + [[package]] 126 + name = "windows-sys" 127 + version = "0.61.2" 128 + source = "registry+https://github.com/rust-lang/crates.io-index" 129 + checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" 130 + dependencies = [ 131 + "windows-link", 132 + ]
+13
Cargo.toml
··· 1 + [package] 2 + name = "porcelain" 3 + version = "0.1.0" 4 + license = "MIT" 5 + authors = ["0xda157 <da157@voidq.com"] 6 + description = "cli dollcode encoder and decoder" 7 + readme = "README.md" 8 + homepage = "https://codeberg.org/da157/porcelain" 9 + repository = "https://codeberg.org/da157/porcelain" 10 + edition = "2024" 11 + 12 + [dependencies] 13 + clap = { version = "4.5.51", features = ["cargo"] }
+22
LICENSE
··· 1 + The MIT License (MIT) 2 + 3 + Copyright © 0xda157 2025 4 + 5 + 6 + Permission is hereby granted, free of charge, to any person obtaining a copy 7 + of this software and associated documentation files (the “Software”), to deal 8 + in the Software without restriction, including without limitation the rights 9 + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 + copies of the Software, and to permit persons to whom the Software is 11 + furnished to do so, subject to the following conditions: 12 + 13 + The above copyright notice and this permission notice shall be included in 14 + all copies or substantial portions of the Software. 15 + 16 + THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 + THE SOFTWARE.
+59
README.md
··· 1 + # porcelain 2 + 3 + cli dollcode encoder and decoder 4 + 5 + ## example usage 6 + 7 + ### decimal 8 + 9 + ```sh 10 + porcelain encode 893271 11 + # ▖▖▘▘▌▘▌▌▘▘▘▌▌ 12 + porcelain decode ▖▖▘▘▌▘▌▌▘▘▘▌▌ 13 + # 893271 14 + ``` 15 + 16 + ### hex 17 + 18 + ```sh 19 + porcelain encode 0xda157 20 + # ▖▖▘▘▌▘▌▌▘▘▘▌▌ 21 + porcelain -x encode da157 # or --hex 22 + # ▖▖▘▘▌▘▌▌▘▘▘▌▌ 23 + porcelain --hex decode ▖▖▘▘▌▘▌▌▘▘▘▌▌ # or -x 24 + # da157 25 + ``` 26 + 27 + ## packaged 28 + 29 + run without installing 30 + 31 + ```sh 32 + nix run https://codeberg.org/da157/porcelain/archive/main.tar.gz -- encode 0xda157 33 + ``` 34 + 35 + install onto nixos system (with flakes) 36 + 37 + ```nix 38 + # flake.nix 39 + { 40 + inputs = { 41 + # ... 42 + porcelain.url = "https://codeberg.org/da157/porcelain/archive/main.tar.gz"; 43 + }; 44 + 45 + outputs = { nixpkgs, ... }@inputs: { 46 + nixosConfigurations.default = nixpkgs.lib.nixosSystem { 47 + modules = [ ./configuration.nix ]; 48 + specialArgs = { inherit inputs; }; 49 + }; 50 + } 51 + } 52 + # configuration.nix 53 + { pkgs, inputs, ... }: 54 + { 55 + environment.systemPackages = [ 56 + inputs.porcelain.packages.${stdenv.hostPlatform.system}.porcelain 57 + ]; 58 + } 59 + ```
+27
flake.lock
··· 1 + { 2 + "nodes": { 3 + "nixpkgs": { 4 + "locked": { 5 + "lastModified": 1762977756, 6 + "narHash": "sha256-4PqRErxfe+2toFJFgcRKZ0UI9NSIOJa+7RXVtBhy4KE=", 7 + "owner": "nixos", 8 + "repo": "nixpkgs", 9 + "rev": "c5ae371f1a6a7fd27823bc500d9390b38c05fa55", 10 + "type": "github" 11 + }, 12 + "original": { 13 + "owner": "nixos", 14 + "ref": "nixos-unstable", 15 + "repo": "nixpkgs", 16 + "type": "github" 17 + } 18 + }, 19 + "root": { 20 + "inputs": { 21 + "nixpkgs": "nixpkgs" 22 + } 23 + } 24 + }, 25 + "root": "root", 26 + "version": 7 27 + }
+29
flake.nix
··· 1 + { 2 + description = "porcelain"; 3 + 4 + inputs.nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; 5 + 6 + outputs = 7 + { nixpkgs, ... }: 8 + let 9 + inherit (nixpkgs) lib; 10 + 11 + forEachPkgs = f: lib.genAttrs lib.systems.flakeExposed (system: f nixpkgs.legacyPackages.${system}); 12 + in 13 + { 14 + packages = forEachPkgs (pkgs: rec { 15 + default = porcelain; 16 + porcelain = pkgs.callPackage ./package.nix { }; 17 + }); 18 + 19 + devShells = forEachPkgs (pkgs: { 20 + default = pkgs.mkShell { 21 + buildInputs = with pkgs; [ 22 + cargo 23 + rust-analyzer 24 + clippy 25 + ]; 26 + }; 27 + }); 28 + }; 29 + }
+31
package.nix
··· 1 + { 2 + lib, 3 + rustPlatform, 4 + }: 5 + let 6 + p = (lib.importTOML ./Cargo.toml).package; 7 + in 8 + rustPlatform.buildRustPackage { 9 + pname = p.name; 10 + inherit (p) version; 11 + 12 + src = lib.fileset.toSource { 13 + root = ./.; 14 + fileset = lib.fileset.intersection (lib.fileset.fromSource (lib.sources.cleanSource ./.)) ( 15 + lib.fileset.unions [ 16 + ./Cargo.toml 17 + ./Cargo.lock 18 + ./src 19 + ] 20 + ); 21 + }; 22 + 23 + cargoLock.lockFile = ./Cargo.lock; 24 + 25 + meta = { 26 + inherit (p) description homepage; 27 + license = lib.licenses.mit; 28 + maintainers = [ lib.maintainers.da157 ]; 29 + mainProgram = "porcelain"; 30 + }; 31 + }
+58
src/lib.rs
··· 1 + pub fn dollcode_decode(input: &str) -> u64 { 2 + input.chars().fold(0, |acc, c| { 3 + acc * 3 4 + + match c { 5 + '▖' => 1, 6 + '▘' => 2, 7 + '▌' => 3, 8 + _ => unreachable!(), 9 + } 10 + }) 11 + } 12 + 13 + pub fn dollcode_encode(mut input: u64) -> String { 14 + let mut res = String::new(); 15 + 16 + while input > 0 { 17 + input -= 1; 18 + res.push(['▖', '▘', '▌'][(input % 3) as usize]); 19 + input /= 3; 20 + } 21 + 22 + res.chars().rev().collect::<String>() 23 + } 24 + 25 + #[cfg(test)] 26 + mod tests { 27 + use crate::{dollcode_decode, dollcode_encode}; 28 + 29 + #[test] 30 + fn test_encode() { 31 + assert_eq!(dollcode_encode(0), ""); 32 + assert_eq!(dollcode_encode(1), "▖"); 33 + assert_eq!(dollcode_encode(2), "▘"); 34 + assert_eq!(dollcode_encode(3), "▌"); 35 + assert_eq!(dollcode_encode(4), "▖▖"); 36 + assert_eq!(dollcode_encode(13), "▖▖▖"); 37 + assert_eq!(dollcode_encode(440729), "▖▌▌▌▌▖▖▖▘▌▘▘"); 38 + assert_eq!( 39 + dollcode_encode(u64::MAX), 40 + "▖▖▖▖▘▘▖▘▌▘▘▖▘▘▖▖▘▌▌▖▖▌▌▌▖▌▖▖▌▖▌▌▖▌▌▘▖▖▘▖▌" 41 + ); 42 + } 43 + 44 + #[test] 45 + fn test_decode() { 46 + assert_eq!(dollcode_decode(""), 0); 47 + assert_eq!(dollcode_decode("▖"), 1); 48 + assert_eq!(dollcode_decode("▘"), 2); 49 + assert_eq!(dollcode_decode("▌"), 3); 50 + assert_eq!(dollcode_decode("▖▖"), 4); 51 + assert_eq!(dollcode_decode("▖▖▖"), 13); 52 + assert_eq!(dollcode_decode("▖▌▌▌▌▖▖▖▘▌▘▘"), 440729); 53 + assert_eq!( 54 + dollcode_decode("▖▖▖▖▘▘▖▘▌▘▘▖▘▘▖▖▘▌▌▖▖▌▌▌▖▌▖▖▌▖▌▌▖▌▌▘▖▖▘▖▌"), 55 + u64::MAX 56 + ); 57 + } 58 + }
+67
src/main.rs
··· 1 + use clap::{Command, arg, command, crate_authors}; 2 + use porcelain::{dollcode_decode, dollcode_encode}; 3 + 4 + fn main() { 5 + let matches = command!() 6 + .author(crate_authors!("\n")) 7 + .subcommand( 8 + Command::new("encode") 9 + .arg(arg!([input] "Input to encode to a dollcode").required(true)), 10 + ) 11 + .subcommand( 12 + Command::new("decode").arg(arg!([dollcode] "Dollcode to decode").required(true)), 13 + ) 14 + .arg( 15 + arg!(hex: -x --hex "Operate in hex mode"), // .conflicts_with("ascii") 16 + ) 17 + // .arg(arg!(ascii: -a --ascii "operate in ascii mode").conflicts_with("hex")) 18 + .get_matches(); 19 + 20 + if let Some(ematches) = matches.subcommand_matches("encode") { 21 + let input = ematches.get_one::<String>("input").unwrap(); 22 + 23 + if matches.get_flag("ascii") { 24 + return; 25 + } 26 + 27 + if let Some(s) = input.strip_prefix("0x") { 28 + println!( 29 + "{}", 30 + dollcode_encode(u64::from_str_radix(s, 16).expect("invalid hex")) 31 + ); 32 + return; 33 + } 34 + 35 + if matches.get_flag("hex") { 36 + println!( 37 + "{}", 38 + dollcode_encode(u64::from_str_radix(input, 16).expect("invalid hex")) 39 + ); 40 + return; 41 + } 42 + 43 + println!( 44 + "{}", 45 + dollcode_encode( 46 + input 47 + .parse::<u64>() 48 + .expect("input must be declared as hex or be a decimial number") 49 + ) 50 + ); 51 + } 52 + 53 + if let Some(dmatches) = matches.subcommand_matches("decode") { 54 + let dollcode = dmatches.get_one::<String>("dollcode").unwrap(); 55 + 56 + if matches.get_flag("ascii") { 57 + return; 58 + } 59 + 60 + if matches.get_flag("hex") { 61 + println!("{:X}", dollcode_decode(dollcode)); 62 + return; 63 + } 64 + 65 + println!("{}", dollcode_decode(dollcode)); 66 + } 67 + }