├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── snapcraft.yaml └── src ├── cat.rs └── main.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /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 = "ansi_term" 7 | version = "0.12.1" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" 10 | dependencies = [ 11 | "winapi 0.3.9", 12 | ] 13 | 14 | [[package]] 15 | name = "arrayvec" 16 | version = "0.5.2" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" 19 | 20 | [[package]] 21 | name = "atty" 22 | version = "0.2.14" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 25 | dependencies = [ 26 | "hermit-abi", 27 | "libc", 28 | "winapi 0.3.9", 29 | ] 30 | 31 | [[package]] 32 | name = "bitflags" 33 | version = "1.3.2" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 36 | 37 | [[package]] 38 | name = "cfg-if" 39 | version = "1.0.0" 40 | source = "registry+https://github.com/rust-lang/crates.io-index" 41 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 42 | 43 | [[package]] 44 | name = "clap" 45 | version = "2.34.0" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" 48 | dependencies = [ 49 | "ansi_term", 50 | "atty", 51 | "bitflags", 52 | "strsim", 53 | "textwrap", 54 | "unicode-width", 55 | "vec_map", 56 | ] 57 | 58 | [[package]] 59 | name = "getrandom" 60 | version = "0.1.16" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" 63 | dependencies = [ 64 | "cfg-if", 65 | "libc", 66 | "wasi", 67 | ] 68 | 69 | [[package]] 70 | name = "hermit-abi" 71 | version = "0.1.19" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 74 | dependencies = [ 75 | "libc", 76 | ] 77 | 78 | [[package]] 79 | name = "kernel32-sys" 80 | version = "0.2.2" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" 83 | dependencies = [ 84 | "winapi 0.2.8", 85 | "winapi-build", 86 | ] 87 | 88 | [[package]] 89 | name = "libc" 90 | version = "0.2.151" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" 93 | 94 | [[package]] 95 | name = "lolcat" 96 | version = "1.5.2" 97 | dependencies = [ 98 | "clap", 99 | "rand", 100 | "termsize", 101 | "utf8-chars", 102 | ] 103 | 104 | [[package]] 105 | name = "numtoa" 106 | version = "0.1.0" 107 | source = "registry+https://github.com/rust-lang/crates.io-index" 108 | checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" 109 | 110 | [[package]] 111 | name = "ppv-lite86" 112 | version = "0.2.17" 113 | source = "registry+https://github.com/rust-lang/crates.io-index" 114 | checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" 115 | 116 | [[package]] 117 | name = "rand" 118 | version = "0.7.3" 119 | source = "registry+https://github.com/rust-lang/crates.io-index" 120 | checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" 121 | dependencies = [ 122 | "getrandom", 123 | "libc", 124 | "rand_chacha", 125 | "rand_core", 126 | "rand_hc", 127 | ] 128 | 129 | [[package]] 130 | name = "rand_chacha" 131 | version = "0.2.2" 132 | source = "registry+https://github.com/rust-lang/crates.io-index" 133 | checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" 134 | dependencies = [ 135 | "ppv-lite86", 136 | "rand_core", 137 | ] 138 | 139 | [[package]] 140 | name = "rand_core" 141 | version = "0.5.1" 142 | source = "registry+https://github.com/rust-lang/crates.io-index" 143 | checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" 144 | dependencies = [ 145 | "getrandom", 146 | ] 147 | 148 | [[package]] 149 | name = "rand_hc" 150 | version = "0.2.0" 151 | source = "registry+https://github.com/rust-lang/crates.io-index" 152 | checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" 153 | dependencies = [ 154 | "rand_core", 155 | ] 156 | 157 | [[package]] 158 | name = "redox_syscall" 159 | version = "0.2.16" 160 | source = "registry+https://github.com/rust-lang/crates.io-index" 161 | checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" 162 | dependencies = [ 163 | "bitflags", 164 | ] 165 | 166 | [[package]] 167 | name = "redox_termios" 168 | version = "0.1.3" 169 | source = "registry+https://github.com/rust-lang/crates.io-index" 170 | checksum = "20145670ba436b55d91fc92d25e71160fbfbdd57831631c8d7d36377a476f1cb" 171 | 172 | [[package]] 173 | name = "strsim" 174 | version = "0.8.0" 175 | source = "registry+https://github.com/rust-lang/crates.io-index" 176 | checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 177 | 178 | [[package]] 179 | name = "termion" 180 | version = "1.5.6" 181 | source = "registry+https://github.com/rust-lang/crates.io-index" 182 | checksum = "077185e2eac69c3f8379a4298e1e07cd36beb962290d4a51199acf0fdc10607e" 183 | dependencies = [ 184 | "libc", 185 | "numtoa", 186 | "redox_syscall", 187 | "redox_termios", 188 | ] 189 | 190 | [[package]] 191 | name = "termsize" 192 | version = "0.1.6" 193 | source = "registry+https://github.com/rust-lang/crates.io-index" 194 | checksum = "5e86d824a8e90f342ad3ef4bd51ef7119a9b681b0cc9f8ee7b2852f02ccd2517" 195 | dependencies = [ 196 | "atty", 197 | "kernel32-sys", 198 | "libc", 199 | "termion", 200 | "winapi 0.2.8", 201 | ] 202 | 203 | [[package]] 204 | name = "textwrap" 205 | version = "0.11.0" 206 | source = "registry+https://github.com/rust-lang/crates.io-index" 207 | checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 208 | dependencies = [ 209 | "unicode-width", 210 | ] 211 | 212 | [[package]] 213 | name = "unicode-width" 214 | version = "0.1.11" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" 217 | 218 | [[package]] 219 | name = "utf8-chars" 220 | version = "1.0.2" 221 | source = "registry+https://github.com/rust-lang/crates.io-index" 222 | checksum = "c1348d8face79d019be7cbc0198e36bf93e160ddbfaa7bb54c9592627b9ec841" 223 | dependencies = [ 224 | "arrayvec", 225 | ] 226 | 227 | [[package]] 228 | name = "vec_map" 229 | version = "0.8.2" 230 | source = "registry+https://github.com/rust-lang/crates.io-index" 231 | checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" 232 | 233 | [[package]] 234 | name = "wasi" 235 | version = "0.9.0+wasi-snapshot-preview1" 236 | source = "registry+https://github.com/rust-lang/crates.io-index" 237 | checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" 238 | 239 | [[package]] 240 | name = "winapi" 241 | version = "0.2.8" 242 | source = "registry+https://github.com/rust-lang/crates.io-index" 243 | checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" 244 | 245 | [[package]] 246 | name = "winapi" 247 | version = "0.3.9" 248 | source = "registry+https://github.com/rust-lang/crates.io-index" 249 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 250 | dependencies = [ 251 | "winapi-i686-pc-windows-gnu", 252 | "winapi-x86_64-pc-windows-gnu", 253 | ] 254 | 255 | [[package]] 256 | name = "winapi-build" 257 | version = "0.1.1" 258 | source = "registry+https://github.com/rust-lang/crates.io-index" 259 | checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" 260 | 261 | [[package]] 262 | name = "winapi-i686-pc-windows-gnu" 263 | version = "0.4.0" 264 | source = "registry+https://github.com/rust-lang/crates.io-index" 265 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 266 | 267 | [[package]] 268 | name = "winapi-x86_64-pc-windows-gnu" 269 | version = "0.4.0" 270 | source = "registry+https://github.com/rust-lang/crates.io-index" 271 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 272 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lolcat" 3 | version = "1.5.2" 4 | authors = ["Umang Raghuvanshi and the lolcat contributors"] 5 | description = "The good ol' lolcat, now with fearless concurrency." 6 | license = "MIT" 7 | repository = "https://github.com/ur0/lolcat" 8 | 9 | [dependencies] 10 | clap = "2.33.0" 11 | rand = "0.7.3" 12 | utf8-chars = "1.0.2" 13 | termsize = "0.1.6" 14 | 15 | [profile.release] 16 | debug = false 17 | lto = true 18 | panic = "abort" 19 | strip = true 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Umang Raghuvanshi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lolcat 2 | 3 | The good ol' lolcat, now with fearless concurrency. 4 | 5 | ![Look at deez colors](http://i.imgur.com/DwZompR.png) 6 | 7 | Run lolcat on everything you like and you'll never have to wonder about the dullness of your life. See? It's that simple. 8 | 9 | ## Installation 10 | 11 | Rustacean? Just run `cargo install lolcat`. 12 | 13 | ## License 14 | 15 | Seriously? It's the [MIT license](LICENSE). 16 | -------------------------------------------------------------------------------- /snapcraft.yaml: -------------------------------------------------------------------------------- 1 | name: lolcat-rs 2 | version: '1.3.2' 3 | summary: Lolcat adds color to your life. 4 | base: core18 5 | description: | 6 | Lolcat-rs is the Rust re-implementation of the original lolcat. It is 7 | fast, beautiful and does not have *any* dependencies. 8 | apps: 9 | lolcat-rs: 10 | command: bin/lolcat 11 | 12 | grade: stable 13 | confinement: strict 14 | 15 | parts: 16 | lolcat: 17 | source: https://github.com/ur0/lolcat.git 18 | plugin: rust 19 | -------------------------------------------------------------------------------- /src/cat.rs: -------------------------------------------------------------------------------- 1 | extern crate rand; 2 | 3 | use rand::Rng; 4 | 5 | use std::thread::sleep; 6 | use std::time::Duration; 7 | 8 | use std::io::stdout; 9 | use std::io::Write; 10 | 11 | // A struct to contain info we need to print with every character 12 | pub struct Control { 13 | pub seed: f64, 14 | pub spread: f64, 15 | pub frequency: f64, 16 | pub background_mode: bool, 17 | pub dialup_mode: bool, 18 | pub print_color: bool, 19 | pub terminal_width_plus_one: u16, 20 | pub terminal_supports_truecolor: bool, 21 | } 22 | 23 | // This used to have more of a reason to exist, however now all its functionality is in 24 | // print_chars_lol(). It takes in an iterator over lines and prints them all. 25 | // At the end, it resets the foreground color 26 | pub fn print_lines_lol, S: AsRef>(lines: I, c: &mut Control) { 27 | for line in lines { 28 | print_chars_lol(line.as_ref().chars().chain(Some('\n')), c, false); 29 | } 30 | if c.print_color { 31 | print!("\x1b[39m"); 32 | } 33 | } 34 | 35 | // Takes in s an iterator over characters 36 | // duplicates escape sequences, otherwise prints printable characters with colored_print 37 | // Print newlines correctly, resetting background 38 | // If constantly_flush is on, it won't wait till a newline to flush stdout 39 | pub fn print_chars_lol>( 40 | mut iter: I, 41 | c: &mut Control, 42 | constantly_flush: bool, 43 | ) { 44 | let mut seed_at_start_of_line = c.seed; 45 | let mut ignoring_whitespace = c.background_mode; 46 | let mut printed_chars_on_line_plus_one = 1u16; 47 | 48 | if !c.print_color { 49 | for character in iter { 50 | print!("{}", character); 51 | } 52 | return; 53 | } 54 | 55 | while let Some(character) = iter.next() { 56 | match character { 57 | // Consume escape sequences 58 | '\x1b' => { 59 | // Escape sequences seem to be one of many different categories: https://en.wikipedia.org/wiki/ANSI_escape_code 60 | // CSI sequences are \e \[ [bytes in 0x30-0x3F] [bytes in 0x20-0x2F] [final byte in 0x40-0x7E] 61 | // nF Escape seq are \e [bytes in 0x20-0x2F] [byte in 0x30-0x7E] 62 | // Fp Escape seq are \e [byte in 0x30-0x3F] [I have no idea, but `sl` creates one where the next byte is the end of the escape sequence, so assume that] 63 | // Fe Escape seq are \e [byte in 0x40-0x5F] [I have no idea, '' though sl doesn't make one] 64 | // Fs Escape seq are \e [byte in 0x60-0x7E] [I have no idea, '' though sl doesn't make one] 65 | // Otherwise the next byte is the whole escape sequence (maybe? I can't exactly tell, but I will go with it) 66 | // We will consume up to, but not through, the next printable character 67 | // In addition, we print everything in the escape sequence, even if it is a color (that will be overriden) 68 | // TODO figure out just how these should affect printed_characters_on_line 69 | print!("\x1b"); 70 | let mut escape_sequence_character = iter 71 | .next() 72 | .expect("Escape character with no escape sequence after it"); 73 | print!("{}", escape_sequence_character); 74 | match escape_sequence_character { 75 | '[' => loop { 76 | escape_sequence_character = 77 | iter.next().expect("CSI escape sequence did not terminate"); 78 | print!("{}", escape_sequence_character); 79 | match escape_sequence_character { 80 | '\x30'..='\x3F' => continue, 81 | '\x20'..='\x2F' => { 82 | loop { 83 | escape_sequence_character = 84 | iter.next().expect("CSI escape sequence did not terminate"); 85 | print!("{}", escape_sequence_character); 86 | match escape_sequence_character { 87 | '\x20' ..= '\x2F' => continue, 88 | '\x40' ..= '\x7E' => break, 89 | _ => panic!("CSI escape sequence terminated with an incorrect value"), 90 | } 91 | } 92 | break; 93 | } 94 | '\x40'..='\x7E' => break, 95 | _ => panic!("CSI escape sequence terminated with an incorrect value"), 96 | } 97 | }, 98 | '\x20'..='\x2F' => loop { 99 | escape_sequence_character = 100 | iter.next().expect("nF escape sequence did not terminate"); 101 | print!("{}", escape_sequence_character); 102 | match escape_sequence_character { 103 | '\x20'..='\x2F' => continue, 104 | '\x30'..='\x7E' => break, 105 | _ => panic!("nF escape sequence terminated with an incorrect value"), 106 | } 107 | }, 108 | // '\x30' ..= '\x3F' => panic!("Fp escape sequences are not supported"), 109 | // '\x40' ..= '\x5F' => panic!("Fe escape sequences are not supported"), 110 | // '\x60' ..= '\x7E' => panic!("Fs escape sequences are not supported"), 111 | // be lazy and assume in all other cases we consume exactly 1 byte 112 | _ => (), 113 | } 114 | } 115 | // Newlines print escape sequences to end background prints, and in dialup mode sleep, and 116 | // reset the seed of the coloring and the value of ignore_whitespace 117 | '\n' => { 118 | handle_newline( 119 | c, 120 | &mut seed_at_start_of_line, 121 | &mut ignoring_whitespace, 122 | &mut printed_chars_on_line_plus_one, 123 | ); 124 | } 125 | // If not an escape sequence or a newline, print a colorful escape sequence and then the 126 | // character 127 | _ => { 128 | if printed_chars_on_line_plus_one == c.terminal_width_plus_one { 129 | handle_newline( 130 | c, 131 | &mut seed_at_start_of_line, 132 | &mut ignoring_whitespace, 133 | &mut printed_chars_on_line_plus_one, 134 | ); 135 | } 136 | // In background mode, don't print colorful whitespace until the first printable character 137 | if ignoring_whitespace && character.is_whitespace() { 138 | print!("{}", character); 139 | continue; 140 | } else { 141 | ignoring_whitespace = false; 142 | } 143 | 144 | colored_print(character, c); 145 | c.seed += 1.0; 146 | printed_chars_on_line_plus_one += 1; 147 | } 148 | } 149 | 150 | // If we should constantly flush, flush after each completed sequence, and also reset 151 | // colors because otherwise weird things happen 152 | if constantly_flush { 153 | reset_colors(c); 154 | stdout().flush().unwrap(); 155 | } 156 | } 157 | } 158 | 159 | fn handle_newline( 160 | c: &mut Control, 161 | seed_at_start_of_line: &mut f64, 162 | ignoring_whitespace: &mut bool, 163 | printed_chars_on_line_plus_one: &mut u16, 164 | ) { 165 | if c.print_color { 166 | // Reset the background color only, as we don't have to reset the foreground till 167 | // the end of the program 168 | // We reset the background here because otherwise it bleeds all the way to the next line 169 | if c.background_mode { 170 | print!("\x1b[49m"); 171 | } 172 | } 173 | println!(); 174 | if c.dialup_mode { 175 | let stall = Duration::from_millis(rand::thread_rng().gen_range(30, 700)); 176 | sleep(stall); 177 | } 178 | 179 | *seed_at_start_of_line += 1.0; 180 | c.seed = *seed_at_start_of_line; // Reset the seed, but bump it a bit 181 | *ignoring_whitespace = c.background_mode; 182 | *printed_chars_on_line_plus_one = 1u16; 183 | } 184 | 185 | fn reset_colors(c: &Control) { 186 | if c.print_color { 187 | // Reset the background color 188 | if c.background_mode { 189 | print!("\x1b[49m"); 190 | } 191 | 192 | // Reset the foreground color 193 | print!("\x1b[39m"); 194 | } 195 | } 196 | 197 | fn colored_print(character: char, c: &mut Control) { 198 | if c.background_mode { 199 | let bg = get_color_tuple(c); 200 | let fg = calc_fg_color(bg); 201 | print!( 202 | "{}{}{}", 203 | rgb_to_256(fg.0, fg.1, fg.2, true, c.terminal_supports_truecolor), 204 | rgb_to_256(bg.0, bg.1, bg.2, false, c.terminal_supports_truecolor), 205 | character 206 | ); 207 | } else { 208 | let fg = get_color_tuple(c); 209 | print!( 210 | "{}{}", 211 | rgb_to_256(fg.0, fg.1, fg.2, true, c.terminal_supports_truecolor), 212 | character 213 | ); 214 | } 215 | } 216 | 217 | fn calc_fg_color(bg: (u8, u8, u8)) -> (u8, u8, u8) { 218 | // Currently, it only computes the forground clolor based on some threshold 219 | // on grayscale value. 220 | // TODO: Add a better algorithm for computing forground color 221 | if conv_grayscale(bg) > 0xA0_u8 { 222 | (0u8, 0u8, 0u8) 223 | } else { 224 | (0xffu8, 0xffu8, 0xffu8) 225 | } 226 | } 227 | 228 | fn linear_to_srgb(intensity: f64) -> f64 { 229 | if intensity <= 0.003_130_8 { 230 | 12.92 * intensity 231 | } else { 232 | 1.055 * intensity.powf(1.0 / 2.4) - 0.055 233 | } 234 | } 235 | 236 | fn srgb_to_linear(intensity: f64) -> f64 { 237 | if intensity < 0.04045 { 238 | intensity / 12.92 239 | } else { 240 | ((intensity + 0.055) / 1.055).powf(2.4) 241 | } 242 | } 243 | 244 | fn conv_grayscale(color: (u8, u8, u8)) -> u8 { 245 | // See https://en.wikipedia.org/wiki/Grayscale#Converting_color_to_grayscale 246 | const SCALE: f64 = 256.0; 247 | 248 | // Changing SRGB to Linear for gamma correction 249 | let red = srgb_to_linear(f64::from(color.0) / SCALE); 250 | let green = srgb_to_linear(f64::from(color.1) / SCALE); 251 | let blue = srgb_to_linear(f64::from(color.2) / SCALE); 252 | 253 | // Converting to grayscale 254 | let gray_linear = red * 0.299 + green * 0.587 + blue * 0.114; 255 | 256 | // Gamma correction 257 | let gray_srgb = linear_to_srgb(gray_linear); 258 | 259 | (gray_srgb * SCALE) as u8 260 | } 261 | 262 | fn get_color_tuple(c: &Control) -> (u8, u8, u8) { 263 | let i = c.frequency * c.seed / c.spread; 264 | let red = i.sin() * 127.00 + 128.00; 265 | let green = (i + (std::f64::consts::PI * 2.00 / 3.00)).sin() * 127.00 + 128.00; 266 | let blue = (i + (std::f64::consts::PI * 4.00 / 3.00)).sin() * 127.00 + 128.00; 267 | 268 | (red as u8, green as u8, blue as u8) 269 | } 270 | 271 | // Returns closest supported 256-color an RGB value 272 | // Inspired by the ruby paint gem 273 | fn rgb_to_256(r: u8, g: u8, b: u8, foreground: bool, use_truecolor: bool) -> String { 274 | let prefix = if foreground { "38" } else { "48" }; 275 | 276 | if use_truecolor { 277 | return format!("\x1b[{};2;{};{};{}m", prefix, r, g, b); 278 | } 279 | 280 | let r = r as f64; 281 | let g = g as f64; 282 | let b = b as f64; 283 | 284 | let colors = [(r, 36), (g, 6), (b, 1)]; 285 | let mut color_code = 16; 286 | for (color, modulator) in &colors { 287 | color_code += ((6.0 * (*color / 256.0)).floor() as u16) * modulator; 288 | } 289 | 290 | format!("\x1b[{};5;{}m", prefix, color_code) 291 | } 292 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate clap; 2 | extern crate rand; 3 | extern crate utf8_chars; 4 | 5 | use clap::{App, Arg}; 6 | use std::fs::File; 7 | use std::io; 8 | use std::io::BufRead; 9 | use std::io::BufReader; 10 | use std::io::IsTerminal; 11 | use std::num::ParseIntError; 12 | use utf8_chars::BufReadCharsExt; 13 | 14 | mod cat; 15 | 16 | fn main() { 17 | let mut filename: String = "".to_string(); 18 | let mut c = parse_cli_args(&mut filename); 19 | 20 | if filename == "" { 21 | let stdin = io::stdin(); // For lifetime reasons 22 | cat::print_chars_lol( 23 | BufReader::new(stdin.lock()).chars().map(|r| r.unwrap()), 24 | &mut c, 25 | true, 26 | ); 27 | } else if lolcat_file(&filename, &mut c).is_err() { 28 | eprintln!("Error opening file {}.", filename) 29 | } 30 | } 31 | 32 | fn lolcat_file(filename: &str, c: &mut cat::Control) -> Result<(), io::Error> { 33 | let f = File::open(filename)?; 34 | let file = BufReader::new(&f); 35 | cat::print_lines_lol(file.lines().map(|r| r.unwrap()), c); 36 | Ok(()) 37 | } 38 | 39 | fn parse_cli_args(filename: &mut String) -> cat::Control { 40 | let matches = lolcat_clap_app().get_matches(); 41 | 42 | let seed = matches.value_of("seed").unwrap_or("0.0"); 43 | let spread = matches.value_of("spread").unwrap_or("3.0"); 44 | let frequency = matches.value_of("frequency").unwrap_or("0.1"); 45 | 46 | let mut seed: f64 = seed.parse().unwrap(); 47 | let spread: f64 = spread.parse().unwrap(); 48 | let frequency: f64 = frequency.parse().unwrap(); 49 | 50 | if seed == 0.0 { 51 | seed = rand::random::() * 10e9; 52 | } 53 | 54 | *filename = matches.value_of("filename").unwrap_or("").to_string(); 55 | 56 | let print_color = matches.is_present("force-color") || std::io::stdout().is_terminal(); 57 | 58 | // If the terminal width is passed, use that. Else, get the size of the terminal. Else, use 0 (no overflow) 59 | let terminal_width: Result = matches.value_of("width") 60 | .unwrap_or("") 61 | .parse(); 62 | let terminal_width: u16 = match terminal_width { 63 | Ok(width) => width, 64 | Err(_) => { 65 | let size = termsize::get(); 66 | match size { 67 | Some(size) => size.cols, 68 | None => 0b11111111_11111111, 69 | } 70 | } 71 | }; 72 | 73 | let terminal_supports_truecolor = match std::env::var("COLORTERM") { 74 | Ok(val) => val == "truecolor" || val == "24bit", 75 | Err(_) => false, 76 | }; 77 | 78 | let mut retval = cat::Control { 79 | seed, 80 | spread, 81 | frequency, 82 | background_mode: matches.is_present("background"), 83 | dialup_mode: matches.is_present("dialup"), 84 | print_color, 85 | terminal_width_plus_one: terminal_width.wrapping_add(1), 86 | terminal_supports_truecolor, 87 | }; 88 | 89 | if matches.is_present("help") { 90 | print_rainbow_help(false, &mut retval); 91 | std::process::exit(0); 92 | } 93 | if matches.is_present("version") { 94 | print_rainbow_help(true, &mut retval); 95 | std::process::exit(0); 96 | } 97 | 98 | retval 99 | } 100 | 101 | fn print_rainbow_help(only_version: bool, c: &mut cat::Control) { 102 | let app = lolcat_clap_app(); 103 | 104 | let mut help = Vec::new(); 105 | if only_version { 106 | app.write_version(&mut help).unwrap(); 107 | } else { 108 | app.write_help(&mut help).unwrap(); 109 | } 110 | let help = String::from_utf8(help).unwrap(); 111 | 112 | cat::print_lines_lol(help.lines(), c); 113 | } 114 | 115 | fn lolcat_clap_app() -> App<'static, 'static> { 116 | App::new("lolcat") 117 | .version(env!("CARGO_PKG_VERSION")) 118 | .author(env!("CARGO_PKG_AUTHORS")) 119 | .about(env!("CARGO_PKG_DESCRIPTION")) 120 | .arg( 121 | Arg::with_name("seed") 122 | .short("s") 123 | .long("seed") 124 | .help("A seed for your lolcat. Setting this to 0 randomizes the seed.") 125 | .takes_value(true), 126 | ) 127 | .arg( 128 | Arg::with_name("spread") 129 | .short("S") 130 | .long("spread") 131 | .help("How much should we spread dem colors? Defaults to 3.0") 132 | .takes_value(true), 133 | ) 134 | .arg( 135 | Arg::with_name("frequency") 136 | .short("f") 137 | .long("frequency") 138 | .help("Frequency - used in our math. Defaults to 0.1") 139 | .takes_value(true), 140 | ) 141 | .arg( 142 | Arg::with_name("background") 143 | .short("B") 144 | .long("bg") 145 | .help("Background mode - If selected the background will be rainbow. Default false") 146 | .takes_value(false), 147 | ) 148 | .arg( 149 | Arg::with_name("dialup") 150 | .short("D") 151 | .long("dialup") 152 | .help("Dialup mode - Simulate dialup connection") 153 | .takes_value(false), 154 | ) 155 | .arg( 156 | Arg::with_name("force-color") 157 | .short("F") 158 | .long("force-color") 159 | .help("Force color - Print escape sequences even if the output is not a terminal") 160 | .takes_value(false), 161 | ) 162 | .arg( 163 | Arg::with_name("width") 164 | .long("terminal-width") 165 | .help("Terminal width - Set a custom terminal wrapping width, or 0 for unlimited") 166 | .takes_value(true), 167 | ) 168 | .arg( 169 | Arg::with_name("filename") 170 | .short("i") 171 | .long("input file name") 172 | .help("Lolcat this file. Reads from STDIN if missing") 173 | .takes_value(true) 174 | .index(1), 175 | ) 176 | .arg( 177 | Arg::with_name("help") 178 | .short("h") 179 | .long("help") 180 | .help("Prints help information") 181 | .takes_value(false), 182 | ) 183 | .arg( 184 | Arg::with_name("version") 185 | .short("V") 186 | .long("version") 187 | .help("Prints version information") 188 | .takes_value(false), 189 | ) 190 | } 191 | --------------------------------------------------------------------------------