Small library for generating claude-code like unicde block mascots, and providing animations when they do stuff
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: initial clood CLI - unicode block character generator

Rust CLI tool that generates cute unicode block characters with
configurable parameters:
- height/width: body dimensions
- armsize: width of each arm
- legsize: length of each leg
- color: hex color for body/arms/legs
- eyecolor: hex color for eyes
- mood: vertical eye position offset from baseline

goose.art e24ee31a

+334
+6
.gitignore
··· 1 + .todos/ 2 + 3 + 4 + # Added by cargo 5 + 6 + /target
+186
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 = "1.0.0" 8 + source = "registry+https://github.com/rust-lang/crates.io-index" 9 + checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" 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.14" 23 + source = "registry+https://github.com/rust-lang/crates.io-index" 24 + checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" 25 + 26 + [[package]] 27 + name = "anstyle-parse" 28 + version = "1.0.0" 29 + source = "registry+https://github.com/rust-lang/crates.io-index" 30 + checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" 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.6.0" 58 + source = "registry+https://github.com/rust-lang/crates.io-index" 59 + checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" 60 + dependencies = [ 61 + "clap_builder", 62 + "clap_derive", 63 + ] 64 + 65 + [[package]] 66 + name = "clap_builder" 67 + version = "4.6.0" 68 + source = "registry+https://github.com/rust-lang/crates.io-index" 69 + checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" 70 + dependencies = [ 71 + "anstream", 72 + "anstyle", 73 + "clap_lex", 74 + "strsim", 75 + ] 76 + 77 + [[package]] 78 + name = "clap_derive" 79 + version = "4.6.0" 80 + source = "registry+https://github.com/rust-lang/crates.io-index" 81 + checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a" 82 + dependencies = [ 83 + "heck", 84 + "proc-macro2", 85 + "quote", 86 + "syn", 87 + ] 88 + 89 + [[package]] 90 + name = "clap_lex" 91 + version = "1.1.0" 92 + source = "registry+https://github.com/rust-lang/crates.io-index" 93 + checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" 94 + 95 + [[package]] 96 + name = "clood" 97 + version = "0.1.0" 98 + dependencies = [ 99 + "clap", 100 + ] 101 + 102 + [[package]] 103 + name = "colorchoice" 104 + version = "1.0.5" 105 + source = "registry+https://github.com/rust-lang/crates.io-index" 106 + checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" 107 + 108 + [[package]] 109 + name = "heck" 110 + version = "0.5.0" 111 + source = "registry+https://github.com/rust-lang/crates.io-index" 112 + checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 113 + 114 + [[package]] 115 + name = "is_terminal_polyfill" 116 + version = "1.70.2" 117 + source = "registry+https://github.com/rust-lang/crates.io-index" 118 + checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" 119 + 120 + [[package]] 121 + name = "once_cell_polyfill" 122 + version = "1.70.2" 123 + source = "registry+https://github.com/rust-lang/crates.io-index" 124 + checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" 125 + 126 + [[package]] 127 + name = "proc-macro2" 128 + version = "1.0.106" 129 + source = "registry+https://github.com/rust-lang/crates.io-index" 130 + checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" 131 + dependencies = [ 132 + "unicode-ident", 133 + ] 134 + 135 + [[package]] 136 + name = "quote" 137 + version = "1.0.45" 138 + source = "registry+https://github.com/rust-lang/crates.io-index" 139 + checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" 140 + dependencies = [ 141 + "proc-macro2", 142 + ] 143 + 144 + [[package]] 145 + name = "strsim" 146 + version = "0.11.1" 147 + source = "registry+https://github.com/rust-lang/crates.io-index" 148 + checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 149 + 150 + [[package]] 151 + name = "syn" 152 + version = "2.0.117" 153 + source = "registry+https://github.com/rust-lang/crates.io-index" 154 + checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" 155 + dependencies = [ 156 + "proc-macro2", 157 + "quote", 158 + "unicode-ident", 159 + ] 160 + 161 + [[package]] 162 + name = "unicode-ident" 163 + version = "1.0.24" 164 + source = "registry+https://github.com/rust-lang/crates.io-index" 165 + checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" 166 + 167 + [[package]] 168 + name = "utf8parse" 169 + version = "0.2.2" 170 + source = "registry+https://github.com/rust-lang/crates.io-index" 171 + checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 172 + 173 + [[package]] 174 + name = "windows-link" 175 + version = "0.2.1" 176 + source = "registry+https://github.com/rust-lang/crates.io-index" 177 + checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" 178 + 179 + [[package]] 180 + name = "windows-sys" 181 + version = "0.61.2" 182 + source = "registry+https://github.com/rust-lang/crates.io-index" 183 + checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" 184 + dependencies = [ 185 + "windows-link", 186 + ]
+8
Cargo.toml
··· 1 + [package] 2 + name = "clood" 3 + version = "0.1.0" 4 + edition = "2021" 5 + description = "Generate cute unicode block characters in your terminal" 6 + 7 + [dependencies] 8 + clap = { version = "4", features = ["derive"] }
+134
src/main.rs
··· 1 + use clap::Parser; 2 + 3 + /// Generate a cute unicode block character (clood) in your terminal. 4 + #[derive(Parser, Debug)] 5 + #[command(name = "clood", version, about)] 6 + struct Args { 7 + /// Height of the body in rows 8 + #[arg(long, default_value_t = 6)] 9 + height: usize, 10 + 11 + /// Width of the body in columns (must be even, min 4) 12 + #[arg(long, default_value_t = 8)] 13 + width: usize, 14 + 15 + /// Width of each arm in columns 16 + #[arg(long, default_value_t = 2)] 17 + armsize: usize, 18 + 19 + /// Length of each leg in rows 20 + #[arg(long, default_value_t = 2)] 21 + legsize: usize, 22 + 23 + /// Color of the body/arms/legs as hex (e.g. #88DDFF) 24 + #[arg(long, default_value = "#88DDFF")] 25 + color: String, 26 + 27 + /// Color of the eyes as hex (e.g. #000000) 28 + #[arg(long, default_value = "#000000")] 29 + eyecolor: String, 30 + 31 + /// How far up the eyes are from the baseline (halfway up body). Positive = higher, negative = lower. 32 + #[arg(long, default_value_t = 0, allow_hyphen_values = true)] 33 + mood: i32, 34 + } 35 + 36 + fn parse_hex_color(hex: &str) -> (u8, u8, u8) { 37 + let hex = hex.trim_start_matches('#'); 38 + let r = u8::from_str_radix(&hex[0..2], 16).unwrap_or(0); 39 + let g = u8::from_str_radix(&hex[2..4], 16).unwrap_or(0); 40 + let b = u8::from_str_radix(&hex[4..6], 16).unwrap_or(0); 41 + (r, g, b) 42 + } 43 + 44 + fn colored_block(r: u8, g: u8, b: u8) -> String { 45 + format!("\x1b[38;2;{};{};{}m\u{2588}\x1b[0m", r, g, b) 46 + } 47 + 48 + fn main() { 49 + let args = Args::parse(); 50 + 51 + let width = args.width.max(4); 52 + let height = args.height.max(3); 53 + let armsize = args.armsize; 54 + let legsize = args.legsize; 55 + 56 + let (cr, cg, cb) = parse_hex_color(&args.color); 57 + let (er, eg, eb) = parse_hex_color(&args.eyecolor); 58 + 59 + let body_block = colored_block(cr, cg, cb); 60 + let eye_block = colored_block(er, eg, eb); 61 + let space = " "; 62 + 63 + // Eye row: baseline is halfway up the body (from bottom), mood shifts it 64 + // Row 0 = top of body, row height-1 = bottom of body 65 + // Baseline = height / 2 (counting from top) 66 + let baseline_row = (height as i32) / 2; 67 + let eye_row = (baseline_row - args.mood).clamp(1, height as i32 - 2) as usize; 68 + 69 + // Eye positions: place two eyes symmetrically, each 1 block wide 70 + // At roughly 1/3 and 2/3 of the width 71 + let eye_left = width / 3; 72 + let eye_right = width - 1 - (width / 3); 73 + 74 + // Arm vertical range: middle portion of the body 75 + let arm_top = height / 3; 76 + let arm_bottom = height - 1 - (height / 3); 77 + 78 + let arm_pad = armsize; // space reserved for arms on each side 79 + let total_width_chars = arm_pad + width + arm_pad; 80 + 81 + let mut output = String::new(); 82 + 83 + // -- Body rows (with arms and eyes) -- 84 + for row in 0..height { 85 + let has_arms = row >= arm_top && row <= arm_bottom; 86 + 87 + for col in 0..total_width_chars { 88 + let in_left_arm = col < arm_pad; 89 + let in_right_arm = col >= arm_pad + width; 90 + let in_body = col >= arm_pad && col < arm_pad + width; 91 + 92 + if in_body { 93 + let body_col = col - arm_pad; 94 + if row == eye_row && (body_col == eye_left || body_col == eye_right) { 95 + output.push_str(&eye_block); 96 + } else { 97 + output.push_str(&body_block); 98 + } 99 + } else if (in_left_arm || in_right_arm) && has_arms { 100 + output.push_str(&body_block); 101 + } else { 102 + output.push_str(space); 103 + } 104 + } 105 + output.push('\n'); 106 + } 107 + 108 + // -- Leg rows -- 109 + // Two legs at the left and right portions of the body, with a gap in the middle 110 + let leg_width = (width / 3).max(1); 111 + let gap = width - 2 * leg_width; 112 + 113 + for _row in 0..legsize { 114 + // Left padding (arm area, no arms on legs) 115 + for _ in 0..arm_pad { 116 + output.push_str(space); 117 + } 118 + // Left leg 119 + for _ in 0..leg_width { 120 + output.push_str(&body_block); 121 + } 122 + // Gap 123 + for _ in 0..gap { 124 + output.push_str(space); 125 + } 126 + // Right leg 127 + for _ in 0..leg_width { 128 + output.push_str(&body_block); 129 + } 130 + output.push('\n'); 131 + } 132 + 133 + print!("{}", output); 134 + }