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: randomize all parameters by default

All params are now Optional. When not specified, they're randomized:
- height: 6..12, width: 8..16, armsize: 2..4, legsize: 2..4
- mood: -2..2
- color and eyecolor: random RGB (components 40..255)

Specifying a flag explicitly pins that value.

+137 -38
+89
Cargo.lock
··· 53 53 ] 54 54 55 55 [[package]] 56 + name = "cfg-if" 57 + version = "1.0.4" 58 + source = "registry+https://github.com/rust-lang/crates.io-index" 59 + checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" 60 + 61 + [[package]] 56 62 name = "clap" 57 63 version = "4.6.0" 58 64 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 97 103 version = "0.1.0" 98 104 dependencies = [ 99 105 "clap", 106 + "rand", 100 107 ] 101 108 102 109 [[package]] ··· 106 113 checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" 107 114 108 115 [[package]] 116 + name = "getrandom" 117 + version = "0.2.17" 118 + source = "registry+https://github.com/rust-lang/crates.io-index" 119 + checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" 120 + dependencies = [ 121 + "cfg-if", 122 + "libc", 123 + "wasi", 124 + ] 125 + 126 + [[package]] 109 127 name = "heck" 110 128 version = "0.5.0" 111 129 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 118 136 checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" 119 137 120 138 [[package]] 139 + name = "libc" 140 + version = "0.2.183" 141 + source = "registry+https://github.com/rust-lang/crates.io-index" 142 + checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" 143 + 144 + [[package]] 121 145 name = "once_cell_polyfill" 122 146 version = "1.70.2" 123 147 source = "registry+https://github.com/rust-lang/crates.io-index" 124 148 checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" 149 + 150 + [[package]] 151 + name = "ppv-lite86" 152 + version = "0.2.21" 153 + source = "registry+https://github.com/rust-lang/crates.io-index" 154 + checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" 155 + dependencies = [ 156 + "zerocopy", 157 + ] 125 158 126 159 [[package]] 127 160 name = "proc-macro2" ··· 142 175 ] 143 176 144 177 [[package]] 178 + name = "rand" 179 + version = "0.8.5" 180 + source = "registry+https://github.com/rust-lang/crates.io-index" 181 + checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 182 + dependencies = [ 183 + "libc", 184 + "rand_chacha", 185 + "rand_core", 186 + ] 187 + 188 + [[package]] 189 + name = "rand_chacha" 190 + version = "0.3.1" 191 + source = "registry+https://github.com/rust-lang/crates.io-index" 192 + checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 193 + dependencies = [ 194 + "ppv-lite86", 195 + "rand_core", 196 + ] 197 + 198 + [[package]] 199 + name = "rand_core" 200 + version = "0.6.4" 201 + source = "registry+https://github.com/rust-lang/crates.io-index" 202 + checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 203 + dependencies = [ 204 + "getrandom", 205 + ] 206 + 207 + [[package]] 145 208 name = "strsim" 146 209 version = "0.11.1" 147 210 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 171 234 checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 172 235 173 236 [[package]] 237 + name = "wasi" 238 + version = "0.11.1+wasi-snapshot-preview1" 239 + source = "registry+https://github.com/rust-lang/crates.io-index" 240 + checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" 241 + 242 + [[package]] 174 243 name = "windows-link" 175 244 version = "0.2.1" 176 245 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 184 253 dependencies = [ 185 254 "windows-link", 186 255 ] 256 + 257 + [[package]] 258 + name = "zerocopy" 259 + version = "0.8.47" 260 + source = "registry+https://github.com/rust-lang/crates.io-index" 261 + checksum = "efbb2a062be311f2ba113ce66f697a4dc589f85e78a4aea276200804cea0ed87" 262 + dependencies = [ 263 + "zerocopy-derive", 264 + ] 265 + 266 + [[package]] 267 + name = "zerocopy-derive" 268 + version = "0.8.47" 269 + source = "registry+https://github.com/rust-lang/crates.io-index" 270 + checksum = "0e8bc7269b54418e7aeeef514aa68f8690b8c0489a06b0136e5f57c4c5ccab89" 271 + dependencies = [ 272 + "proc-macro2", 273 + "quote", 274 + "syn", 275 + ]
+1
Cargo.toml
··· 6 6 7 7 [dependencies] 8 8 clap = { version = "4", features = ["derive"] } 9 + rand = "0.8"
+47 -38
src/main.rs
··· 1 1 use clap::Parser; 2 + use rand::Rng; 2 3 3 4 /// Generate a cute unicode block character (clood) in your terminal. 5 + /// All parameters are randomized by default (between 1x and 2x base values). 6 + /// Specify a flag explicitly to pin that value. 4 7 #[derive(Parser, Debug)] 5 8 #[command(name = "clood", version, about)] 6 9 struct Args { 7 - /// Height of the body in rows 8 - #[arg(long, default_value_t = 6)] 9 - height: usize, 10 + /// Height of the body in rows [default: random 6..12] 11 + #[arg(long)] 12 + height: Option<usize>, 10 13 11 - /// Width of the body in columns (must be even, min 4) 12 - #[arg(long, default_value_t = 8)] 13 - width: usize, 14 + /// Width of the body in columns [default: random 8..16] 15 + #[arg(long)] 16 + width: Option<usize>, 14 17 15 - /// Width of each arm in columns 16 - #[arg(long, default_value_t = 2)] 17 - armsize: usize, 18 + /// Width of each arm in columns [default: random 2..4] 19 + #[arg(long)] 20 + armsize: Option<usize>, 18 21 19 - /// Length of each leg in rows 20 - #[arg(long, default_value_t = 2)] 21 - legsize: usize, 22 + /// Length of each leg in rows [default: random 2..4] 23 + #[arg(long)] 24 + legsize: Option<usize>, 22 25 23 - /// Color of the body/arms/legs as hex (e.g. #88DDFF) 24 - #[arg(long, default_value = "#88DDFF")] 25 - color: String, 26 + /// Color of the body/arms/legs as hex (e.g. #88DDFF) [default: random] 27 + #[arg(long)] 28 + color: Option<String>, 26 29 27 - /// Color of the eyes as hex (e.g. #000000) 28 - #[arg(long, default_value = "#000000")] 29 - eyecolor: String, 30 + /// Color of the eyes as hex (e.g. #000000) [default: random] 31 + #[arg(long)] 32 + eyecolor: Option<String>, 30 33 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 + /// How far up the eyes are from the baseline (halfway up body). Positive = higher, negative = lower. [default: random -2..2] 35 + #[arg(long, allow_hyphen_values = true)] 36 + mood: Option<i32>, 34 37 } 35 38 36 39 fn parse_hex_color(hex: &str) -> (u8, u8, u8) { ··· 39 42 let g = u8::from_str_radix(&hex[2..4], 16).unwrap_or(0); 40 43 let b = u8::from_str_radix(&hex[4..6], 16).unwrap_or(0); 41 44 (r, g, b) 45 + } 46 + 47 + fn random_color(rng: &mut impl Rng) -> (u8, u8, u8) { 48 + (rng.gen_range(40..=255), rng.gen_range(40..=255), rng.gen_range(40..=255)) 42 49 } 43 50 44 51 fn colored_block(r: u8, g: u8, b: u8) -> String { ··· 47 54 48 55 fn main() { 49 56 let args = Args::parse(); 57 + let mut rng = rand::thread_rng(); 50 58 51 - let width = args.width.max(4); 52 - let height = args.height.max(3); 53 - let armsize = args.armsize; 54 - let legsize = args.legsize; 59 + // Defaults: random between base and 2x base 60 + let width = args.width.unwrap_or_else(|| rng.gen_range(8..=16)).max(4); 61 + let height = args.height.unwrap_or_else(|| rng.gen_range(6..=12)).max(3); 62 + let armsize = args.armsize.unwrap_or_else(|| rng.gen_range(2..=4)); 63 + let legsize = args.legsize.unwrap_or_else(|| rng.gen_range(2..=4)); 64 + let mood = args.mood.unwrap_or_else(|| rng.gen_range(-2..=2)); 55 65 56 - let (cr, cg, cb) = parse_hex_color(&args.color); 57 - let (er, eg, eb) = parse_hex_color(&args.eyecolor); 66 + let (cr, cg, cb) = args.color 67 + .as_deref() 68 + .map(parse_hex_color) 69 + .unwrap_or_else(|| random_color(&mut rng)); 70 + 71 + let (er, eg, eb) = args.eyecolor 72 + .as_deref() 73 + .map(parse_hex_color) 74 + .unwrap_or_else(|| random_color(&mut rng)); 58 75 59 76 let body_block = colored_block(cr, cg, cb); 60 77 let eye_block = colored_block(er, eg, eb); 61 78 let space = " "; 62 79 63 80 // 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 81 let baseline_row = (height as i32) / 2; 67 - let eye_row = (baseline_row - args.mood).clamp(1, height as i32 - 2) as usize; 82 + let eye_row = (baseline_row - mood).clamp(1, height as i32 - 2) as usize; 68 83 69 - // Eye positions: place two eyes symmetrically, each 1 block wide 70 - // At roughly 1/3 and 2/3 of the width 84 + // Eye positions: place two eyes symmetrically at ~1/3 and ~2/3 of width 71 85 let eye_left = width / 3; 72 86 let eye_right = width - 1 - (width / 3); 73 87 ··· 75 89 let arm_top = height / 3; 76 90 let arm_bottom = height - 1 - (height / 3); 77 91 78 - let arm_pad = armsize; // space reserved for arms on each side 92 + let arm_pad = armsize; 79 93 let total_width_chars = arm_pad + width + arm_pad; 80 94 81 95 let mut output = String::new(); ··· 106 120 } 107 121 108 122 // -- Leg rows -- 109 - // Two legs at the left and right portions of the body, with a gap in the middle 110 123 let leg_width = (width / 3).max(1); 111 124 let gap = width - 2 * leg_width; 112 125 113 126 for _row in 0..legsize { 114 - // Left padding (arm area, no arms on legs) 115 127 for _ in 0..arm_pad { 116 128 output.push_str(space); 117 129 } 118 - // Left leg 119 130 for _ in 0..leg_width { 120 131 output.push_str(&body_block); 121 132 } 122 - // Gap 123 133 for _ in 0..gap { 124 134 output.push_str(space); 125 135 } 126 - // Right leg 127 136 for _ in 0..leg_width { 128 137 output.push_str(&body_block); 129 138 }