├── README.md ├── chip8-emulator ├── Cargo.toml ├── README.md └── src │ ├── .gitignore │ ├── audio.rs │ ├── cpu.rs │ ├── main.rs │ ├── util.rs │ └── window.rs ├── rust-2d-rpg ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md └── src │ ├── assets │ ├── enemy.png │ ├── player.png │ └── tile.png │ ├── enemy.rs │ ├── game.rs │ ├── main.rs │ ├── map.rs │ └── player.rs ├── rust-file-encryptor ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md └── src │ ├── cli.rs │ ├── encryption.rs │ ├── io.rs │ └── main.rs ├── rust-tui-chat ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md └── src │ ├── main.rs │ ├── message.rs │ ├── network.rs │ └── ui.rs ├── rust-web-scraper ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md └── src │ ├── main.rs │ ├── models.rs │ └── scraper.rs ├── rust_temperature_converter ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md └── src │ └── main.rs └── todo_list ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md └── src └── main.rs /README.md: -------------------------------------------------------------------------------- 1 | # 7Days7RustProjects 2 | 3 | A collection of Rust projects designed to guide beginners through various programming concepts, from basic CLI tools to advanced emulation. Each project increases in difficulty, promoting a comprehensive learning curve. 4 | 5 | ## Overview 6 | 7 | This repository contains: 8 | 9 | - **Day 1**: Command Line Todo List 🐚 10 | - **Day 2**: Temperature Converter GUI 🌡️ 11 | - **Day 3**: File Encryptor/Decryptor 🔒 12 | - **Day 4**: Web Scraper with Actix Web 🕸️ 13 | - **Day 5**: 2D Game Renderer 🎮 14 | - **Day 6**: TUI Chat Application 💬 15 | - **Day 7**: CHIP-8 Emulator 👾 16 | 17 | ## Projects 18 | 19 | ### Day 1: CLI Todo List 20 | - **Difficulty**: Beginner 21 | - **Features**: Add, remove, list todos. 22 | 23 | ### Day 2: Temperature Converter GUI 24 | - **Difficulty**: Beginner-Intermediate 25 | - **Features**: Convert between Celsius and Fahrenheit. 26 | 27 | ### Day 3: File Encryptor/Decryptor 28 | - **Difficulty**: Intermediate 29 | - **Features**: Basic file encryption/decryption. 30 | 31 | ### Day 4: Web Scraper with Actix 32 | - **Difficulty**: Intermediate 33 | - **Features**: Scrape website data and serve via HTTP. 34 | 35 | ### Day 5: 2D Game Renderer 36 | - **Difficulty**: Intermediate-Advanced 37 | - **Features**: Simple 2D graphics rendering. 38 | 39 | ### Day 6: TUI Chat Application 40 | - **Difficulty**: Advanced 41 | - **Features**: Terminal-based chat with networking. 42 | 43 | ### Day 7: CHIP-8 Emulator 44 | - **Difficulty**: Advanced 45 | - **Features**: Emulation of CHIP-8 games. 46 | 47 | ## How to Use 48 | 49 | Each project folder contains: 50 | 51 | - **/src**: Source code 52 | - **Cargo.toml**: Project dependencies 53 | - **README.md**: Project-specific instructions 54 | 55 | To run a project: 56 | 1. Navigate to the project directory: 57 | ```bash 58 | cd dayX_project_name -------------------------------------------------------------------------------- /chip8-emulator/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "chip8-emulator" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | minifb = "0.23.0" 8 | rodio = "0.15" 9 | rand = "0.7" -------------------------------------------------------------------------------- /chip8-emulator/README.md: -------------------------------------------------------------------------------- 1 | ### **Day 7: Building a CHIP-8 Emulator in Rust - An Advanced Adventure** 2 | 3 | #### Overview 4 | Today's project is a significant step up in complexity as we build a CHIP-8 emulator. CHIP-8 is an interpreted programming language, allowing us to dive into low-level programming concepts, emulation, and understanding of vintage computing architecture. 5 | 6 | #### Difficulty 7 | 🏔️ **Advanced** 8 | 9 | #### Prerequisites 10 | - Solid understanding of Rust 11 | - Basic knowledge of computer architecture 12 | - Familiarity with emulators or game consoles 13 | 14 | #### Project Structure 15 | First, let's set up our project structure: 16 | 17 | ```sh 18 | mkdir chip8-emulator 19 | cd chip8-emulator 20 | cargo init --bin 21 | ``` 22 | 23 | Now, let's organize our source code: 24 | 25 | ``` 26 | chip8-emulator/ 27 | │ 28 | ├── src/ 29 | │ ├── audio.rs 30 | │ ├── cpu.rs 31 | │ ├── main.rs 32 | │ ├── util.rs 33 | │ └── window.rs 34 | │ 35 | ├── Cargo.toml 36 | └── README.md 37 | ``` 38 | 39 | #### Step 1: Setting up `Cargo.toml` 40 | 41 | ```toml 42 | [package] 43 | name = "chip8-emulator" 44 | version = "0.1.0" 45 | edition = "2018" 46 | 47 | [dependencies] 48 | minifb = "0.19" # For window creation 49 | rodio = "0.14" # For audio 50 | ``` 51 | 52 | #### Step 2: `audio.rs` - Handling Beep Sound 53 | 54 | ```rust 55 | use rodio::{ 56 | Sink, 57 | OutputStream 58 | }; 59 | 60 | pub struct Audio { 61 | sink: Sink, 62 | _stream: OutputStream 63 | } 64 | 65 | impl Audio { 66 | pub fn new() -> Result { 67 | let (stream, stream_handle) = match OutputStream::try_default() { 68 | Ok(v) => v, 69 | Err(err) => { return Err(err.to_string()); } 70 | }; 71 | let sink = match Sink::try_new(&stream_handle) { 72 | Ok(v) => v, 73 | Err(err) => { return Err(err.to_string()); } 74 | }; 75 | sink.append(rodio::source::SineWave::new(440.0)); 76 | sink.pause(); 77 | let ret = Audio {sink, _stream: stream}; 78 | Ok(ret) 79 | } 80 | 81 | pub fn play(&self) { 82 | self.sink.play(); 83 | } 84 | 85 | pub fn pause(&self) { 86 | self.sink.pause(); 87 | } 88 | } 89 | ``` 90 | 91 | #### Step 3: `cpu.rs` - The Core of the Emulator 92 | 93 | ```rust 94 | use minifb::Key; 95 | 96 | use crate::audio::Audio; 97 | use crate::window::Window; 98 | use crate::util::{ 99 | get_bit, 100 | get_hex_digits 101 | }; 102 | 103 | const RAM_SIZE: usize = 4096; 104 | const REGISTER_COUNT: usize = 16; 105 | const STACK_SIZE: usize = 16; 106 | const RUNLOOP_TIMER_DEFAULT: usize = 8; 107 | const PROGRAM_START: usize = 0x200; 108 | 109 | const RAM_DIGITS: [[u8; 5]; 16] = [ 110 | [0xf0, 0x90, 0x90, 0x90, 0xf0], 111 | [0x20, 0x60, 0x20, 0x20, 0x70], 112 | [0xf0, 0x10, 0xf0, 0x80, 0xf0], 113 | [0xf0, 0x10, 0xf0, 0x10, 0xf0], 114 | [0x90, 0x90, 0xf0, 0x10, 0x10], 115 | [0xf0, 0x80, 0xf0, 0x10, 0xf0], 116 | [0xf0, 0x80, 0xf0, 0x90, 0xf0], 117 | [0xf0, 0x10, 0x20, 0x40, 0x40], 118 | [0xf0, 0x90, 0xf0, 0x90, 0xf0], 119 | [0xf0, 0x90, 0xf0, 0x10, 0xf0], 120 | [0xf0, 0x90, 0xf0, 0x90, 0x90], 121 | [0xe0, 0x90, 0xe0, 0x90, 0xe0], 122 | [0xf0, 0x80, 0x80, 0x80, 0xf0], 123 | [0xe0, 0x90, 0x90, 0x90, 0xe0], 124 | [0xf0, 0x80, 0xf0, 0x80, 0xf0], 125 | [0xf0, 0x80, 0xf0, 0x80, 0x80] 126 | ]; 127 | 128 | pub struct CPU { 129 | ram: [u8; RAM_SIZE], 130 | v: [u8; REGISTER_COUNT], 131 | i: usize, 132 | dt: u8, 133 | st: u8, 134 | stack: [usize; STACK_SIZE], 135 | sp: usize, 136 | pc: usize, 137 | win: Window, 138 | audio: Audio 139 | } 140 | 141 | impl CPU { 142 | pub fn new(win: Window, audio: Audio) -> CPU { 143 | let mut ret = CPU { 144 | ram: [0; RAM_SIZE], 145 | v: [0; REGISTER_COUNT], 146 | i: 0, 147 | dt: 0, 148 | st: 0, 149 | stack: [0; STACK_SIZE], 150 | sp: 0, 151 | pc: PROGRAM_START, 152 | win, 153 | audio 154 | }; 155 | ret.preload_ram(); 156 | ret 157 | } 158 | 159 | pub fn load_rom(&mut self, rom: &Vec) -> Result<(), &str> { 160 | if PROGRAM_START + rom.len() >= RAM_SIZE { 161 | return Err("Out of memory: program too large"); 162 | } 163 | for (j, c) in rom.into_iter().enumerate() { 164 | self.ram[j + PROGRAM_START] = *c; 165 | } 166 | Ok(()) 167 | } 168 | 169 | fn preload_ram(&mut self) { 170 | for (j, d) in RAM_DIGITS.iter().enumerate() { 171 | for (k, b) in d.iter().enumerate() { 172 | self.ram[(0x10 * j) + k] = *b; 173 | } 174 | } 175 | } 176 | 177 | pub fn run_loop(&mut self) -> Result<(), &str> { 178 | let mut executing = true; 179 | let mut waiting_for_keypress = false; 180 | let mut store_keypress_in: usize = 0x0; 181 | let mut time_to_runloop: usize = RUNLOOP_TIMER_DEFAULT; 182 | 183 | while self.win.is_open() && !self.win.is_key_down(Key::Escape) && self.pc <= RAM_SIZE { 184 | let keys_pressed = self.win.handle_key_events(); 185 | 186 | for (j, k) in keys_pressed.iter().enumerate() { 187 | if *k { 188 | if waiting_for_keypress { 189 | executing = true; 190 | waiting_for_keypress = false; 191 | self.v[store_keypress_in] = j as u8; 192 | break; 193 | } 194 | println!("{:01x} pressed!", j); 195 | } 196 | } 197 | 198 | let b1 = self.ram[self.pc] as u16; 199 | let b2 = self.ram[self.pc + 1] as u16; 200 | let instruction = (b1 * 256) + b2; 201 | let mut next_instruction = true; 202 | 203 | 204 | if executing { 205 | println!("{:03x}, {:04x}, {:04x}, {:02x?}", self.pc, instruction, self.i, self.v); 206 | match instruction { 207 | 0x00e0 => { 208 | self.win.clear_screen(); 209 | }, 210 | 0x00ee => { 211 | if self.sp == 0 { 212 | return Err("Stack empty, cannot return from subroutine!"); 213 | } 214 | self.sp -= 1; 215 | self.pc = self.stack[self.sp]; 216 | }, 217 | 0x1000..=0x1fff => { 218 | self.pc = get_hex_digits(&instruction, 3, 0); 219 | next_instruction = false; 220 | }, 221 | 0x2000..=0x2fff => { 222 | let loc = get_hex_digits(&instruction, 3, 0); 223 | if self.sp == STACK_SIZE { 224 | return Err("Stack full, cannot push!"); 225 | } 226 | self.stack[self.sp] = self.pc; 227 | self.sp += 1; 228 | self.pc = loc; 229 | next_instruction = false; 230 | }, 231 | 0x3000..=0x3fff => { 232 | let val = get_hex_digits(&instruction, 2, 0); 233 | let reg = get_hex_digits(&instruction, 1, 2); 234 | if self.v[reg] == val as u8 { 235 | self.pc += 2; 236 | } 237 | }, 238 | 0x4000..=0x4fff => { 239 | let val = get_hex_digits(&instruction, 2, 0); 240 | let reg = get_hex_digits(&instruction, 1, 2); 241 | if self.v[reg] != val as u8 { 242 | self.pc += 2; 243 | } 244 | }, 245 | 0x5000..=0x5fff => { 246 | let reg1 = get_hex_digits(&instruction, 1, 2); 247 | let reg2 = get_hex_digits(&instruction, 1, 1); 248 | if self.v[reg1] == self.v[reg2] { 249 | self.pc += 2; 250 | } 251 | }, 252 | 0x6000..=0x6fff => { 253 | let val = get_hex_digits(&instruction, 2, 0); 254 | let reg = get_hex_digits(&instruction, 1, 2); 255 | self.v[reg] = val as u8; 256 | }, 257 | 0x7000..=0x7fff => { 258 | let val = get_hex_digits(&instruction, 2, 0); 259 | let reg = get_hex_digits(&instruction, 1, 2); 260 | self.v[reg] = self.v[reg].overflowing_add(val as u8).0; 261 | }, 262 | 0x8000..=0x8fff => { 263 | let lsb = get_hex_digits(&instruction, 1, 0); 264 | let reg1 = get_hex_digits(&instruction, 1, 2); 265 | let reg2 = get_hex_digits(&instruction, 1, 1); 266 | 267 | match lsb { 268 | 0x0 => { 269 | self.v[reg1] = self.v[reg2]; 270 | }, 271 | 0x1 => { 272 | self.v[reg1] |= self.v[reg2]; 273 | }, 274 | 0x2 => { 275 | self.v[reg1] &= self.v[reg2]; 276 | }, 277 | 0x3 => {y 278 | self.v[reg1] ^= self.v[reg2]; 279 | }, 280 | 0x4 => { 281 | let (res, over) = self.v[reg1].overflowing_add(self.v[reg2]); 282 | self.v[reg1] = res; 283 | self.v[0xf] = if over {1} else {0}; 284 | }, 285 | 0x5 => { 286 | let (res, over) = self.v[reg1].overflowing_sub(self.v[reg2]); 287 | self.v[reg1] = res; 288 | self.v[0xf] = if over {0} else {1}; 289 | }, 290 | 0x6 => { 291 | let res = self.v[reg1].overflowing_shr(1).0; 292 | self.v[0xf] = get_bit(&self.v[reg1], 0); 293 | self.v[reg1] = res; 294 | }, 295 | 0x7 => { 296 | let (res, over) = self.v[reg2].overflowing_sub(self.v[reg1]); 297 | self.v[reg1] = res; 298 | self.v[0xf] = if over {0} else {1}; 299 | }, 300 | 0xe => { 301 | let res = self.v[reg1].overflowing_shl(1).0; 302 | self.v[0xf] = get_bit(&self.v[reg1], 7); 303 | self.v[reg1] = res; 304 | }, 305 | _ => { 306 | println!("Warning: unrecognized instruction: {:04x}", instruction); 307 | } 308 | }; 309 | }, 310 | 0x9000..=0x9fff => { 311 | let reg1 = get_hex_digits(&instruction, 1, 2); 312 | let reg2 = get_hex_digits(&instruction, 1, 1); 313 | if self.v[reg1] != self.v[reg2] { 314 | self.pc += 2; 315 | } 316 | }, 317 | 0xa000..=0xafff => { 318 | self.i = get_hex_digits(&instruction, 3, 0); 319 | }, 320 | 0xb000..=0xbfff => { 321 | self.pc = get_hex_digits(&instruction, 3, 0) + self.v[0] as usize; 322 | next_instruction = false; 323 | }, 324 | 0xc000..=0xcfff => { 325 | let rnd = rand::random::(); 326 | let val = get_hex_digits(&instruction, 2, 0); 327 | let reg = get_hex_digits(&instruction, 1, 2); 328 | self.v[reg] = rnd & val as u8; 329 | }, 330 | 0xd000..=0xdfff => { 331 | let reg1 = get_hex_digits(&instruction, 1, 2); 332 | let reg2 = get_hex_digits(&instruction, 1, 1); 333 | let init_x = self.v[reg1]; 334 | let init_y = self.v[reg2]; 335 | let mut byte_count = get_hex_digits(&instruction, 1, 0); 336 | let mut bytes_to_print: Vec = Vec::new(); 337 | let mut j = 0; 338 | while byte_count > 0 { 339 | bytes_to_print.push(self.ram[self.i + j]); 340 | byte_count -= 1; 341 | j += 1; 342 | } 343 | self.v[0xf] = self.win.draw(&bytes_to_print, init_x, init_y); 344 | }, 345 | 0xe000..=0xff65 => { 346 | let d1 = get_hex_digits(&instruction, 1, 3); 347 | let d2 = get_hex_digits(&instruction, 1, 2); 348 | let d3 = get_hex_digits(&instruction, 1, 1); 349 | let d4 = get_hex_digits(&instruction, 1, 0); 350 | 351 | if d1 == 0xe && d3 == 0x9 && d4 == 0xe { 352 | if keys_pressed[self.v[d2] as usize] { 353 | self.pc += 2; 354 | } 355 | } 356 | 357 | else if d1 == 0xe && d3 == 0xa && d4 == 0x1 { 358 | if !keys_pressed[self.v[d2] as usize] { 359 | self.pc += 2; 360 | } 361 | } 362 | 363 | else if d1 == 0xf && d3 == 0x0 && d4 == 0x7 { 364 | self.v[d2] = self.dt; 365 | } 366 | 367 | else if d1 == 0xf && d3 == 0x0 && d4 == 0xa { 368 | executing = false; 369 | waiting_for_keypress = true; 370 | store_keypress_in = d2; 371 | } 372 | 373 | else if d1 == 0xf && d3 == 0x1 && d4 == 0x5 { 374 | self.dt = self.v[d2]; 375 | } 376 | 377 | else if d1 == 0xf && d3 == 0x1 && d4 == 0x8 { 378 | self.st = self.v[d2]; 379 | } 380 | 381 | else if d1 == 0xf && d3 == 0x1 && d4 == 0xe { 382 | self.i += self.v[d2] as usize; 383 | } 384 | 385 | else if d1 == 0xf && d3 == 0x2 && d4 == 0x9 { 386 | self.i = (0x10 * self.v[d2]) as usize; 387 | } 388 | 389 | else if d1 == 0xf && d3 == 0x3 && d4 == 0x3 { 390 | ) 391 | self.ram[self.i] = self.v[d2] / 100; 392 | self.ram[self.i+1] = (self.v[d2] % 100) / 10; 393 | self.ram[self.i+2] = self.v[d2] % 10; 394 | } 395 | 396 | else if d1 == 0xf && d3 == 0x5 && d4 == 0x5 { 397 | for j in 0..=d2 { 398 | self.ram[self.i+j] = self.v[j]; 399 | } 400 | } 401 | 402 | else if d1 == 0xf && d3 == 0x6 && d4 == 0x5 { 403 | for j in 0..=d2 { 404 | self.v[j] = self.ram[self.i+j]; 405 | } 406 | } 407 | 408 | else { 409 | println!("Warning: unrecognized instruction: {:04x}", instruction); 410 | } 411 | }, 412 | _ => { 413 | println!("Warning: unrecognized instruction: {:04x}", instruction); 414 | } 415 | }; 416 | 417 | if next_instruction { 418 | self.pc += 2; 419 | } 420 | } 421 | 422 | if time_to_runloop == 0 { 423 | if self.dt > 0 { self.dt -= 1; } 424 | 425 | if self.st > 0 { 426 | self.audio.play(); 427 | self.st -= 1; 428 | } 429 | else if self.st == 0 { 430 | self.audio.pause(); 431 | } 432 | 433 | self.win.refresh(); 434 | 435 | time_to_runloop = RUNLOOP_TIMER_DEFAULT; 436 | } 437 | else { 438 | time_to_runloop -= 1; 439 | } 440 | } 441 | Ok(()) 442 | } 443 | } 444 | ``` 445 | 446 | #### Step 4: `window.rs` - Display and Input 447 | 448 | ```rust 449 | use minifb::{ 450 | Key, 451 | WindowOptions, 452 | Scale, 453 | Error 454 | }; 455 | 456 | use crate::util::is_bit_set; 457 | 458 | const WIDTH: usize = 64; 459 | const HEIGHT: usize = 32; 460 | const PX_OFF: u32 = 0x81c784; 461 | const PX_ON: u32 = 0x29302a; 462 | 463 | pub struct Window { 464 | win: minifb::Window, 465 | framebuffer: [u32; WIDTH * HEIGHT] 466 | } 467 | 468 | impl Window { 469 | pub fn new(title: &str) -> Result { 470 | let mut win = match minifb::Window::new( 471 | title, 472 | WIDTH, 473 | HEIGHT, 474 | WindowOptions { 475 | scale: Scale::X8, 476 | ..WindowOptions::default() 477 | } 478 | ) { 479 | Ok(win) => win, 480 | Err(err) => { 481 | return Err(err); 482 | } 483 | }; 484 | // 480 Hz 485 | win.limit_update_rate(Some(std::time::Duration::from_micros(2083))); 486 | Ok(Window { win, framebuffer: [PX_OFF; WIDTH * HEIGHT] }) 487 | } 488 | 489 | pub fn handle_key_events(&self) -> [bool; 16] { 490 | let mut keys = [false; 16]; 491 | self.win.get_keys().iter().for_each(|k| { 492 | match k { 493 | Key::Key1 => keys[0x1] = true, 494 | Key::Key2 => keys[0x2] = true, 495 | Key::Key3 => keys[0x3] = true, 496 | Key::Key4 => keys[0xc] = true, 497 | Key::Q => keys[0x4] = true, 498 | Key::W => keys[0x5] = true, 499 | Key::E => keys[0x6] = true, 500 | Key::R => keys[0xd] = true, 501 | Key::A => keys[0x7] = true, 502 | Key::S => keys[0x8] = true, 503 | Key::D => keys[0x9] = true, 504 | Key::F => keys[0xe] = true, 505 | Key::Z => keys[0xa] = true, 506 | Key::X => keys[0x0] = true, 507 | Key::C => keys[0xb] = true, 508 | Key::V => keys[0xf] = true, 509 | _ => () 510 | }; 511 | }); 512 | keys 513 | } 514 | 515 | pub fn is_key_down(&self, key: Key) -> bool { 516 | self.win.is_key_down(key) 517 | } 518 | 519 | pub fn is_open(&self) -> bool { 520 | self.win.is_open() 521 | } 522 | 523 | pub fn clear_screen(&mut self) { 524 | for j in 0..self.framebuffer.len() { 525 | self.framebuffer[j] = PX_OFF; 526 | } 527 | } 528 | 529 | pub fn draw(&mut self, bytes: &Vec, init_x: u8, init_y: u8) -> u8 { 530 | let mut collision: u8 = 0; 531 | for (k, b) in bytes.iter().enumerate() { 532 | for j in 0..8 { 533 | let x = (init_x as usize + j) % WIDTH; 534 | let y = (init_y as usize + k) % HEIGHT; 535 | let coord = (y * WIDTH) + x; 536 | let is_old_set = self.framebuffer[coord] == PX_ON; 537 | self.framebuffer[coord] = if is_bit_set(b, (8-j-1) as u8) { 538 | if is_old_set { collision = 1; PX_OFF } 539 | else { PX_ON } 540 | } else { self.framebuffer[coord] }; 541 | } 542 | } 543 | collision 544 | } 545 | 546 | pub fn refresh(&mut self) { 547 | self.win.update_with_buffer(&self.framebuffer, WIDTH, HEIGHT).unwrap(); 548 | } 549 | } 550 | ``` 551 | 552 | #### Step 5: `util.rs` - Utility Functions 553 | 554 | ```rust 555 | pub fn get_hex_digits(n: &u16, d: u32, o: u32) -> usize { 556 | let base: u16 = 0x10; 557 | ((n / base.pow(o)) % base.pow(d)) as usize 558 | } 559 | 560 | pub fn is_bit_set(byte: &u8, n: u8) -> bool { 561 | if byte & (1 << n) == 0 { false } else { true } 562 | } 563 | 564 | pub fn get_bit(byte: &u8, n: u8) -> u8 { 565 | if is_bit_set(byte, n) { 1 } else { 0 } 566 | } 567 | ``` 568 | 569 | #### Step 6: `main.rs` - Orchestrating Everything 570 | 571 | ```rust 572 | extern crate minifb; 573 | extern crate rand; 574 | extern crate rodio; 575 | 576 | use std::{ 577 | fs, 578 | env 579 | }; 580 | 581 | mod cpu; 582 | use cpu::CPU; 583 | 584 | mod audio; 585 | use audio::Audio; 586 | 587 | mod window; 588 | use window::Window; 589 | 590 | mod util; 591 | 592 | fn main() { 593 | println!("chip8-rust: CHIP-8 emulator written in Rust"); 594 | 595 | let args: Vec = env::args().collect(); 596 | 597 | if args.len() != 2 { 598 | return eprintln!("Usage: {} ", args[0]); 599 | } 600 | 601 | let filename = String::from(&args[1]); 602 | 603 | let rom = match fs::read(&filename) { 604 | Err(why) => { 605 | return eprintln!("Could not open file: {}", why.to_string()); 606 | }, 607 | Ok(file) => file 608 | }; 609 | 610 | let audio = match Audio::new() { 611 | Ok(a) => a, 612 | Err(err) => { 613 | return eprintln!("Could not initialize audio device: {}", err); 614 | } 615 | }; 616 | 617 | let win = match Window::new(&format!("chip8-rust: {}", filename)) { 618 | Ok(win) => win, 619 | Err(err) => { 620 | return eprintln!("Could not initialize window: {}", &err.to_string()); 621 | } 622 | }; 623 | 624 | let mut cpu = CPU::new(win, audio); 625 | match cpu.load_rom(&rom) { 626 | Ok(()) => (), 627 | Err(err) => { 628 | return eprintln!("Could not initialize CPU: {}", err); 629 | } 630 | }; 631 | 632 | match cpu.run_loop() { 633 | Ok(()) => (), 634 | Err(err) => { 635 | return eprintln!("CPU crashed: {}", err); 636 | } 637 | } 638 | } 639 | ``` 640 | 641 | #### Step 7: Running Your Emulator 642 | 643 | To run: 644 | 645 | ```sh 646 | sudo apt-get install libsdl2-dev 647 | ``` 648 | 649 | ```sh 650 | cargo run romfile.ch8 651 | ``` 652 | 653 | #### Explanation 654 | 655 | - **Audio**: We use `rodio` to handle audio, creating a simple beep sound for the CHIP-8's sound timer. 656 | - **CPU**: This is where the magic happens. The CPU fetches, decodes, and executes opcodes. You'd implement specific instructions here. 657 | - **Window**: Using `minifb`, we create a window to display the CHIP-8 graphics and handle input. 658 | - **Main**: Brings everything together, running the emulation loop. 659 | 660 | #### Conclusion 661 | 662 | Building a CHIP-8 emulator is a deep dive into low-level programming, teaching you about memory management, instruction sets, and real-time systems. While this example provides a skeleton, full implementation requires handling all CHIP-8 instructions, managing timers, and ensuring accurate emulation speed. Extend this project by: 663 | - Implementing full CHIP-8 opcode support. 664 | - Adding proper cycle timing for different CHIP-8 versions. 665 | - Improving the audio system for varied tones or volume control. 666 | 667 | This project offers a hands-on look at how vintage games were run and opens doors to understanding modern emulation techniques. -------------------------------------------------------------------------------- /chip8-emulator/src/.gitignore: -------------------------------------------------------------------------------- 1 | /target -------------------------------------------------------------------------------- /chip8-emulator/src/audio.rs: -------------------------------------------------------------------------------- 1 | use rodio::{OutputStream, Sink}; 2 | 3 | pub struct Audio { 4 | sink: Sink, 5 | _stream: OutputStream, 6 | } 7 | 8 | impl Audio { 9 | pub fn new() -> Result { 10 | let (stream, stream_handle) = match OutputStream::try_default() { 11 | Ok(v) => v, 12 | Err(err) => { 13 | return Err(err.to_string()); 14 | } 15 | }; 16 | let sink = match Sink::try_new(&stream_handle) { 17 | Ok(v) => v, 18 | Err(err) => { 19 | return Err(err.to_string()); 20 | } 21 | }; 22 | sink.append(rodio::source::SineWave::new(440.0)); 23 | sink.pause(); 24 | let ret = Audio { 25 | sink, 26 | _stream: stream, 27 | }; 28 | Ok(ret) 29 | } 30 | 31 | pub fn play(&self) { 32 | self.sink.play(); 33 | } 34 | 35 | pub fn pause(&self) { 36 | self.sink.pause(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /chip8-emulator/src/cpu.rs: -------------------------------------------------------------------------------- 1 | use minifb::Key; 2 | 3 | use crate::audio::Audio; 4 | use crate::util::{get_bit, get_hex_digits}; 5 | use crate::window::Window; 6 | 7 | const RAM_SIZE: usize = 4096; 8 | const REGISTER_COUNT: usize = 16; 9 | const STACK_SIZE: usize = 16; 10 | const RUNLOOP_TIMER_DEFAULT: usize = 8; 11 | const PROGRAM_START: usize = 0x200; 12 | 13 | const RAM_DIGITS: [[u8; 5]; 16] = [ 14 | [0xf0, 0x90, 0x90, 0x90, 0xf0], 15 | [0x20, 0x60, 0x20, 0x20, 0x70], 16 | [0xf0, 0x10, 0xf0, 0x80, 0xf0], 17 | [0xf0, 0x10, 0xf0, 0x10, 0xf0], 18 | [0x90, 0x90, 0xf0, 0x10, 0x10], 19 | [0xf0, 0x80, 0xf0, 0x10, 0xf0], 20 | [0xf0, 0x80, 0xf0, 0x90, 0xf0], 21 | [0xf0, 0x10, 0x20, 0x40, 0x40], 22 | [0xf0, 0x90, 0xf0, 0x90, 0xf0], 23 | [0xf0, 0x90, 0xf0, 0x10, 0xf0], 24 | [0xf0, 0x90, 0xf0, 0x90, 0x90], 25 | [0xe0, 0x90, 0xe0, 0x90, 0xe0], 26 | [0xf0, 0x80, 0x80, 0x80, 0xf0], 27 | [0xe0, 0x90, 0x90, 0x90, 0xe0], 28 | [0xf0, 0x80, 0xf0, 0x80, 0xf0], 29 | [0xf0, 0x80, 0xf0, 0x80, 0x80], 30 | ]; 31 | 32 | pub struct CPU { 33 | ram: [u8; RAM_SIZE], 34 | v: [u8; REGISTER_COUNT], 35 | i: usize, 36 | dt: u8, 37 | st: u8, 38 | stack: [usize; STACK_SIZE], 39 | sp: usize, 40 | pc: usize, 41 | win: Window, 42 | audio: Audio, 43 | } 44 | 45 | impl CPU { 46 | pub fn new(win: Window, audio: Audio) -> CPU { 47 | let mut ret = CPU { 48 | ram: [0; RAM_SIZE], 49 | v: [0; REGISTER_COUNT], 50 | i: 0, 51 | dt: 0, 52 | st: 0, 53 | stack: [0; STACK_SIZE], 54 | sp: 0, 55 | pc: PROGRAM_START, 56 | win, 57 | audio, 58 | }; 59 | ret.preload_ram(); 60 | ret 61 | } 62 | 63 | pub fn load_rom(&mut self, rom: &Vec) -> Result<(), &str> { 64 | if PROGRAM_START + rom.len() >= RAM_SIZE { 65 | return Err("Out of memory: program too large"); 66 | } 67 | for (j, c) in rom.into_iter().enumerate() { 68 | self.ram[j + PROGRAM_START] = *c; 69 | } 70 | Ok(()) 71 | } 72 | 73 | fn preload_ram(&mut self) { 74 | for (j, d) in RAM_DIGITS.iter().enumerate() { 75 | for (k, b) in d.iter().enumerate() { 76 | self.ram[(0x10 * j) + k] = *b; 77 | } 78 | } 79 | } 80 | 81 | pub fn run_loop(&mut self) -> Result<(), &str> { 82 | let mut executing = true; 83 | let mut waiting_for_keypress = false; 84 | let mut store_keypress_in: usize = 0x0; 85 | let mut time_to_runloop: usize = RUNLOOP_TIMER_DEFAULT; 86 | 87 | while self.win.is_open() && !self.win.is_key_down(Key::Escape) && self.pc <= RAM_SIZE { 88 | let keys_pressed = self.win.handle_key_events(); 89 | 90 | for (j, k) in keys_pressed.iter().enumerate() { 91 | if *k { 92 | if waiting_for_keypress { 93 | executing = true; 94 | waiting_for_keypress = false; 95 | self.v[store_keypress_in] = j as u8; 96 | break; 97 | } 98 | println!("{:01x} pressed!", j); 99 | } 100 | } 101 | 102 | let b1 = self.ram[self.pc] as u16; 103 | let b2 = self.ram[self.pc + 1] as u16; 104 | let instruction = (b1 * 256) + b2; 105 | 106 | let mut next_instruction = true; 107 | 108 | if executing { 109 | println!( 110 | "{:03x}, {:04x}, {:04x}, {:02x?}", 111 | self.pc, instruction, self.i, self.v 112 | ); 113 | match instruction { 114 | 0x00e0 => { 115 | self.win.clear_screen(); 116 | } 117 | 0x00ee => { 118 | if self.sp == 0 { 119 | return Err("Stack empty, cannot return from subroutine!"); 120 | } 121 | self.sp -= 1; 122 | self.pc = self.stack[self.sp]; 123 | } 124 | 0x1000..=0x1fff => { 125 | self.pc = get_hex_digits(&instruction, 3, 0); 126 | next_instruction = false; 127 | } 128 | 0x2000..=0x2fff => { 129 | let loc = get_hex_digits(&instruction, 3, 0); 130 | if self.sp == STACK_SIZE { 131 | return Err("Stack full, cannot push!"); 132 | } 133 | self.stack[self.sp] = self.pc; 134 | self.sp += 1; 135 | self.pc = loc; 136 | next_instruction = false; 137 | } 138 | 0x3000..=0x3fff => { 139 | let val = get_hex_digits(&instruction, 2, 0); 140 | let reg = get_hex_digits(&instruction, 1, 2); 141 | if self.v[reg] == val as u8 { 142 | self.pc += 2; 143 | } 144 | } 145 | 0x4000..=0x4fff => { 146 | let val = get_hex_digits(&instruction, 2, 0); 147 | let reg = get_hex_digits(&instruction, 1, 2); 148 | if self.v[reg] != val as u8 { 149 | self.pc += 2; 150 | } 151 | } 152 | 0x5000..=0x5fff => { 153 | let reg1 = get_hex_digits(&instruction, 1, 2); 154 | let reg2 = get_hex_digits(&instruction, 1, 1); 155 | if self.v[reg1] == self.v[reg2] { 156 | self.pc += 2; 157 | } 158 | } 159 | 0x6000..=0x6fff => { 160 | let val = get_hex_digits(&instruction, 2, 0); 161 | let reg = get_hex_digits(&instruction, 1, 2); 162 | self.v[reg] = val as u8; 163 | } 164 | 0x7000..=0x7fff => { 165 | let val = get_hex_digits(&instruction, 2, 0); 166 | let reg = get_hex_digits(&instruction, 1, 2); 167 | self.v[reg] = self.v[reg].overflowing_add(val as u8).0; 168 | } 169 | 0x8000..=0x8fff => { 170 | let lsb = get_hex_digits(&instruction, 1, 0); 171 | let reg1 = get_hex_digits(&instruction, 1, 2); 172 | let reg2 = get_hex_digits(&instruction, 1, 1); 173 | 174 | match lsb { 175 | 0x0 => { 176 | self.v[reg1] = self.v[reg2]; 177 | } 178 | 0x1 => { 179 | self.v[reg1] |= self.v[reg2]; 180 | } 181 | 0x2 => { 182 | self.v[reg1] &= self.v[reg2]; 183 | } 184 | 0x3 => { 185 | self.v[reg1] ^= self.v[reg2]; 186 | } 187 | 0x4 => { 188 | let (res, over) = self.v[reg1].overflowing_add(self.v[reg2]); 189 | self.v[reg1] = res; 190 | self.v[0xf] = if over { 1 } else { 0 }; 191 | } 192 | 0x5 => { 193 | let (res, over) = self.v[reg1].overflowing_sub(self.v[reg2]); 194 | self.v[reg1] = res; 195 | self.v[0xf] = if over { 0 } else { 1 }; 196 | } 197 | 0x6 => { 198 | let res = self.v[reg1].overflowing_shr(1).0; 199 | self.v[0xf] = get_bit(&self.v[reg1], 0); 200 | self.v[reg1] = res; 201 | } 202 | 0x7 => { 203 | let (res, over) = self.v[reg2].overflowing_sub(self.v[reg1]); 204 | self.v[reg1] = res; 205 | self.v[0xf] = if over { 0 } else { 1 }; 206 | } 207 | 0xe => { 208 | let res = self.v[reg1].overflowing_shl(1).0; 209 | self.v[0xf] = get_bit(&self.v[reg1], 7); 210 | self.v[reg1] = res; 211 | } 212 | _ => { 213 | println!("Warning: unrecognized instruction: {:04x}", instruction); 214 | } 215 | }; 216 | } 217 | 0x9000..=0x9fff => { 218 | let reg1 = get_hex_digits(&instruction, 1, 2); 219 | let reg2 = get_hex_digits(&instruction, 1, 1); 220 | if self.v[reg1] != self.v[reg2] { 221 | self.pc += 2; 222 | } 223 | } 224 | 0xa000..=0xafff => { 225 | self.i = get_hex_digits(&instruction, 3, 0); 226 | } 227 | 0xb000..=0xbfff => { 228 | self.pc = get_hex_digits(&instruction, 3, 0) + self.v[0] as usize; 229 | next_instruction = false; 230 | } 231 | 0xc000..=0xcfff => { 232 | let rnd = rand::random::(); 233 | let val = get_hex_digits(&instruction, 2, 0); 234 | let reg = get_hex_digits(&instruction, 1, 2); 235 | self.v[reg] = rnd & val as u8; 236 | } 237 | 0xd000..=0xdfff => { 238 | let reg1 = get_hex_digits(&instruction, 1, 2); 239 | let reg2 = get_hex_digits(&instruction, 1, 1); 240 | let init_x = self.v[reg1]; 241 | let init_y = self.v[reg2]; 242 | let mut byte_count = get_hex_digits(&instruction, 1, 0); 243 | let mut bytes_to_print: Vec = Vec::new(); 244 | let mut j = 0; 245 | while byte_count > 0 { 246 | bytes_to_print.push(self.ram[self.i + j]); 247 | byte_count -= 1; 248 | j += 1; 249 | } 250 | self.v[0xf] = self.win.draw(&bytes_to_print, init_x, init_y); 251 | } 252 | 0xe000..=0xff65 => { 253 | let d1 = get_hex_digits(&instruction, 1, 3); 254 | let d2 = get_hex_digits(&instruction, 1, 2); 255 | let d3 = get_hex_digits(&instruction, 1, 1); 256 | let d4 = get_hex_digits(&instruction, 1, 0); 257 | 258 | if d1 == 0xe && d3 == 0x9 && d4 == 0xe { 259 | if keys_pressed[self.v[d2] as usize] { 260 | self.pc += 2; 261 | } 262 | } else if d1 == 0xe && d3 == 0xa && d4 == 0x1 { 263 | if !keys_pressed[self.v[d2] as usize] { 264 | self.pc += 2; 265 | } 266 | } else if d1 == 0xf && d3 == 0x0 && d4 == 0x7 { 267 | self.v[d2] = self.dt; 268 | } else if d1 == 0xf && d3 == 0x0 && d4 == 0xa { 269 | executing = false; 270 | waiting_for_keypress = true; 271 | store_keypress_in = d2; 272 | } else if d1 == 0xf && d3 == 0x1 && d4 == 0x5 { 273 | self.dt = self.v[d2]; 274 | } else if d1 == 0xf && d3 == 0x1 && d4 == 0x8 { 275 | self.st = self.v[d2]; 276 | } else if d1 == 0xf && d3 == 0x1 && d4 == 0xe { 277 | self.i += self.v[d2] as usize; 278 | } else if d1 == 0xf && d3 == 0x2 && d4 == 0x9 { 279 | self.i = (0x10 * self.v[d2]) as usize; 280 | } else if d1 == 0xf && d3 == 0x3 && d4 == 0x3 { 281 | self.ram[self.i] = self.v[d2] / 100; 282 | self.ram[self.i + 1] = (self.v[d2] % 100) / 10; 283 | self.ram[self.i + 2] = self.v[d2] % 10; 284 | } else if d1 == 0xf && d3 == 0x5 && d4 == 0x5 { 285 | for j in 0..=d2 { 286 | self.ram[self.i + j] = self.v[j]; 287 | } 288 | } else if d1 == 0xf && d3 == 0x6 && d4 == 0x5 { 289 | for j in 0..=d2 { 290 | self.v[j] = self.ram[self.i + j]; 291 | } 292 | } else { 293 | println!("Warning: unrecognized instruction: {:04x}", instruction); 294 | } 295 | } 296 | _ => { 297 | println!("Warning: unrecognized instruction: {:04x}", instruction); 298 | } 299 | }; 300 | 301 | if next_instruction { 302 | self.pc += 2; 303 | } 304 | } 305 | 306 | if time_to_runloop == 0 { 307 | if self.dt > 0 { 308 | self.dt -= 1; 309 | } 310 | 311 | if self.st > 0 { 312 | self.audio.play(); 313 | self.st -= 1; 314 | } else if self.st == 0 { 315 | self.audio.pause(); 316 | } 317 | 318 | self.win.refresh(); 319 | 320 | time_to_runloop = RUNLOOP_TIMER_DEFAULT; 321 | } else { 322 | time_to_runloop -= 1; 323 | } 324 | } 325 | Ok(()) 326 | } 327 | } 328 | -------------------------------------------------------------------------------- /chip8-emulator/src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate minifb; 2 | extern crate rand; 3 | extern crate rodio; 4 | 5 | use std::{env, fs}; 6 | 7 | mod cpu; 8 | use cpu::CPU; 9 | 10 | mod audio; 11 | use audio::Audio; 12 | 13 | mod window; 14 | use window::Window; 15 | 16 | mod util; 17 | 18 | fn main() { 19 | println!("chip8-rust: CHIP-8 emulator written in Rust"); 20 | 21 | let args: Vec = env::args().collect(); 22 | 23 | if args.len() != 2 { 24 | return eprintln!("Usage: {} ", args[0]); 25 | } 26 | 27 | let filename = String::from(&args[1]); 28 | 29 | let rom = match fs::read(&filename) { 30 | Err(why) => { 31 | return eprintln!("Could not open file: {}", why.to_string()); 32 | } 33 | Ok(file) => file, 34 | }; 35 | 36 | let audio = match Audio::new() { 37 | Ok(a) => a, 38 | Err(err) => { 39 | return eprintln!("Could not initialize audio device: {}", err); 40 | } 41 | }; 42 | 43 | let win = match Window::new(&format!("chip8-rust: {}", filename)) { 44 | Ok(win) => win, 45 | Err(err) => { 46 | return eprintln!("Could not initialize window: {}", &err.to_string()); 47 | } 48 | }; 49 | 50 | let mut cpu = CPU::new(win, audio); 51 | match cpu.load_rom(&rom) { 52 | Ok(()) => (), 53 | Err(err) => { 54 | return eprintln!("Could not initialize CPU: {}", err); 55 | } 56 | }; 57 | 58 | match cpu.run_loop() { 59 | Ok(()) => (), 60 | Err(err) => { 61 | return eprintln!("CPU crashed: {}", err); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /chip8-emulator/src/util.rs: -------------------------------------------------------------------------------- 1 | pub fn get_hex_digits(n: &u16, d: u32, o: u32) -> usize { 2 | let base: u16 = 0x10; 3 | ((n / base.pow(o)) % base.pow(d)) as usize 4 | } 5 | 6 | pub fn is_bit_set(byte: &u8, n: u8) -> bool { 7 | if byte & (1 << n) == 0 { 8 | false 9 | } else { 10 | true 11 | } 12 | } 13 | 14 | pub fn get_bit(byte: &u8, n: u8) -> u8 { 15 | if is_bit_set(byte, n) { 16 | 1 17 | } else { 18 | 0 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /chip8-emulator/src/window.rs: -------------------------------------------------------------------------------- 1 | use minifb::{Error, Key, Scale, WindowOptions}; 2 | 3 | use crate::util::is_bit_set; 4 | 5 | const WIDTH: usize = 64; 6 | const HEIGHT: usize = 32; 7 | const PX_OFF: u32 = 0x81c784; 8 | const PX_ON: u32 = 0x29302a; 9 | 10 | pub struct Window { 11 | win: minifb::Window, 12 | framebuffer: [u32; WIDTH * HEIGHT], 13 | } 14 | 15 | impl Window { 16 | pub fn new(title: &str) -> Result { 17 | let mut win = match minifb::Window::new( 18 | title, 19 | WIDTH, 20 | HEIGHT, 21 | WindowOptions { 22 | scale: Scale::X8, 23 | ..WindowOptions::default() 24 | }, 25 | ) { 26 | Ok(win) => win, 27 | Err(err) => { 28 | return Err(err); 29 | } 30 | }; 31 | win.limit_update_rate(Some(std::time::Duration::from_micros(2083))); 32 | Ok(Window { 33 | win, 34 | framebuffer: [PX_OFF; WIDTH * HEIGHT], 35 | }) 36 | } 37 | 38 | pub fn handle_key_events(&self) -> [bool; 16] { 39 | let mut keys = [false; 16]; 40 | self.win.get_keys().iter().for_each(|k| { 41 | match k { 42 | Key::Key1 => keys[0x1] = true, 43 | Key::Key2 => keys[0x2] = true, 44 | Key::Key3 => keys[0x3] = true, 45 | Key::Key4 => keys[0xc] = true, 46 | Key::Q => keys[0x4] = true, 47 | Key::W => keys[0x5] = true, 48 | Key::E => keys[0x6] = true, 49 | Key::R => keys[0xd] = true, 50 | Key::A => keys[0x7] = true, 51 | Key::S => keys[0x8] = true, 52 | Key::D => keys[0x9] = true, 53 | Key::F => keys[0xe] = true, 54 | Key::Z => keys[0xa] = true, 55 | Key::X => keys[0x0] = true, 56 | Key::C => keys[0xb] = true, 57 | Key::V => keys[0xf] = true, 58 | _ => (), 59 | }; 60 | }); 61 | keys 62 | } 63 | 64 | pub fn is_key_down(&self, key: Key) -> bool { 65 | self.win.is_key_down(key) 66 | } 67 | 68 | pub fn is_open(&self) -> bool { 69 | self.win.is_open() 70 | } 71 | 72 | pub fn clear_screen(&mut self) { 73 | for j in 0..self.framebuffer.len() { 74 | self.framebuffer[j] = PX_OFF; 75 | } 76 | } 77 | 78 | pub fn draw(&mut self, bytes: &Vec, init_x: u8, init_y: u8) -> u8 { 79 | let mut collision: u8 = 0; 80 | for (k, b) in bytes.iter().enumerate() { 81 | for j in 0..8 { 82 | let x = (init_x as usize + j) % WIDTH; 83 | let y = (init_y as usize + k) % HEIGHT; 84 | let coord = (y * WIDTH) + x; 85 | let is_old_set = self.framebuffer[coord] == PX_ON; 86 | self.framebuffer[coord] = if is_bit_set(b, (8 - j - 1) as u8) { 87 | if is_old_set { 88 | collision = 1; 89 | PX_OFF 90 | } else { 91 | PX_ON 92 | } 93 | } else { 94 | self.framebuffer[coord] 95 | }; 96 | } 97 | } 98 | collision 99 | } 100 | 101 | pub fn refresh(&mut self) { 102 | self.win 103 | .update_with_buffer(&self.framebuffer, WIDTH, HEIGHT) 104 | .unwrap(); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /rust-2d-rpg/.gitignore: -------------------------------------------------------------------------------- 1 | /target -------------------------------------------------------------------------------- /rust-2d-rpg/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rust-2d-rpg" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | ggez = "0.6.1" 8 | rand = "0.8.4" -------------------------------------------------------------------------------- /rust-2d-rpg/README.md: -------------------------------------------------------------------------------- 1 | ### **Day 5: 2D Game Renderer in Rust - Let's Make a Mini RPG!** 2 | 3 | #### Introduction 4 | Welcome to the world of game development in Rust! Today, we'll build a simple 2D RPG game renderer. We'll use `ggez` for our graphics needs, focusing on creating a game where players can move a character around a tile-based map, interact with objects, and perhaps even battle some basic enemies. 5 | 6 | #### Difficulty 7 | 🌳 **Intermediate-Advanced** 8 | 9 | #### Prerequisites 10 | - Intermediate Rust knowledge 11 | - Basic understanding of 2D graphics 12 | - Familiarity with game loop concepts 13 | 14 | #### Project Structure 15 | Let's organize our project: 16 | 17 | ```sh 18 | mkdir rust-2d-rpg 19 | cd rust-2d-rpg 20 | cargo init --bin 21 | ``` 22 | 23 | Here’s our folder structure: 24 | 25 | ``` 26 | rust-2d-rpg/ 27 | │ 28 | ├── src/ 29 | │ ├── main.rs 30 | │ ├── game.rs 31 | │ ├── map.rs 32 | │ ├── player.rs 33 | │ ├── enemy.rs 34 | │ └── assets/ 35 | │ ├── player.png 36 | │ ├── enemy.png 37 | │ └── tile.png 38 | │ 39 | ├── Cargo.toml 40 | └── README.md 41 | ``` 42 | 43 | #### Step 1: Setting Up `Cargo.toml` 44 | 45 | Add the following dependencies to your `Cargo.toml`: 46 | 47 | ```toml 48 | [package] 49 | name = "rust-2d-rpg" 50 | version = "0.1.0" 51 | edition = "2018" 52 | 53 | [dependencies] 54 | ggez = "0.6.1" 55 | rand = "0.8.4" 56 | ``` 57 | 58 | #### Step 2: `main.rs` - The Entry Point 59 | 60 | ```rust 61 | mod game; 62 | mod map; 63 | mod player; 64 | mod enemy; 65 | 66 | use ggez::{Context, ContextBuilder, GameResult}; 67 | use ggez::event::{self, EventHandler}; 68 | 69 | fn main() -> GameResult { 70 | let cb = ContextBuilder::new("rust_2d_rpg", "YourName") 71 | .window_setup(ggez::conf::WindowSetup::default().title("2D RPG in Rust")); 72 | let (mut ctx, event_loop) = cb.build()?; 73 | let game = game::Game::new(&mut ctx)?; 74 | event::run(ctx, event_loop, game) 75 | } 76 | ``` 77 | 78 | #### Step 3: `game.rs` - Game Logic 79 | 80 | ```rust 81 | use ggez::{Context, GameResult}; 82 | use ggez::event::{self, EventHandler}; 83 | use ggez::graphics; 84 | 85 | pub struct Game { 86 | player: player::Player, 87 | map: map::Map, 88 | enemies: Vec, 89 | } 90 | 91 | impl Game { 92 | pub fn new(ctx: &mut Context) -> GameResult { 93 | let player = player::Player::new(ctx)?; 94 | let map = map::Map::new(); 95 | let enemies = vec![enemy::Enemy::new(ctx)?]; 96 | Ok(Game { player, map, enemies }) 97 | } 98 | } 99 | 100 | impl EventHandler for Game { 101 | fn update(&mut self, _ctx: &mut Context) -> GameResult { 102 | // Update logic for player movement, enemy behavior, etc. 103 | Ok(()) 104 | } 105 | 106 | fn draw(&mut self, ctx: &mut Context) -> GameResult { 107 | graphics::clear(ctx, [0.1, 0.2, 0.3, 1.0].into()); 108 | self.map.draw(ctx)?; 109 | self.player.draw(ctx)?; 110 | for enemy in &self.enemies { 111 | enemy.draw(ctx)?; 112 | } 113 | graphics::present(ctx)?; 114 | Ok(()) 115 | } 116 | } 117 | ``` 118 | 119 | #### Step 4: `player.rs` - Player Management 120 | 121 | ```rust 122 | use ggez::{Context, GameResult}; 123 | use ggez::graphics::{self, Rect}; 124 | use ggez::nalgebra as na; 125 | 126 | pub struct Player { 127 | position: na::Point2, 128 | } 129 | 130 | impl Player { 131 | pub fn new(_ctx: &mut Context) -> GameResult { 132 | Ok(Player { position: na::Point2::new(100.0, 100.0) }) 133 | } 134 | 135 | pub fn draw(&self, ctx: &mut Context) -> GameResult { 136 | let rectangle = graphics::Mesh::new_rectangle( 137 | ctx, 138 | graphics::DrawMode::fill(), 139 | Rect::new(self.position.x, self.position.y, 32.0, 32.0), 140 | graphics::Color::new(0.0, 1.0, 0.0, 1.0), 141 | )?; 142 | graphics::draw(ctx, &rectangle, (na::Point2::new(0.0, 0.0),)) 143 | } 144 | 145 | // Add methods for movement, collision detection, etc. 146 | } 147 | ``` 148 | 149 | #### Step 5: `map.rs` - World Generation 150 | 151 | ```rust 152 | use ggez::{Context, GameResult}; 153 | use ggez::graphics; 154 | 155 | pub struct Map { 156 | // Simplified map representation 157 | tiles: Vec>, 158 | } 159 | 160 | impl Map { 161 | pub fn new() -> Map { 162 | let tiles = vec![vec![true; 20]; 15]; 163 | Map { tiles } 164 | } 165 | 166 | pub fn draw(&self, ctx: &mut Context) -> GameResult { 167 | let tile_size = 32.0; 168 | for (y, row) in self.tiles.iter().enumerate() { 169 | for (x, &tile) in row.iter().enumerate() { 170 | if tile { 171 | let rectangle = graphics::Mesh::new_rectangle( 172 | ctx, 173 | graphics::DrawMode::fill(), 174 | graphics::Rect::new((x as f32) * tile_size, (y as f32) * tile_size, tile_size, tile_size), 175 | [0.5, 0.5, 0.5, 1.0].into(), 176 | )?; 177 | graphics::draw(ctx, &rectangle, (na::Point2::new(0.0, 0.0),))?; 178 | } 179 | } 180 | } 181 | Ok(()) 182 | } 183 | } 184 | ``` 185 | 186 | #### Step 6: `enemy.rs` - Enemies Management 187 | 188 | ```rust 189 | use ggez::{Context, GameResult}; 190 | use ggez::graphics; 191 | use ggez::nalgebra as na; 192 | 193 | pub struct Enemy { 194 | position: na::Point2, 195 | } 196 | 197 | impl Enemy { 198 | pub fn new(ctx: &mut Context) -> GameResult { 199 | Ok(Enemy { position: na::Point2::new(200.0, 200.0) }) 200 | } 201 | 202 | pub fn draw(&self, ctx: &mut Context) -> GameResult { 203 | let rectangle = graphics::Mesh::new_rectangle( 204 | ctx, 205 | graphics::DrawMode::fill(), 206 | graphics::Rect::new(self.position.x, self.position.y, 32.0, 32.0), 207 | graphics::Color::new(1.0, 0.0, 0.0, 1.0), 208 | )?; 209 | graphics::draw(ctx, &rectangle, (na::Point2::new(0.0, 0.0),)) 210 | } 211 | 212 | // Add methods for enemy movement, AI, etc. 213 | } 214 | ``` 215 | 216 | #### Step 7: Running the Game 217 | 218 | To run the game: 219 | 220 | ```sh 221 | cargo run 222 | ``` 223 | 224 | #### Explanation 225 | 226 | - **Main Loop**: In `main.rs`, we set up the ggez context and our game loop. 227 | - **Game State**: `game.rs` holds the overall game state, including the player, map, and enemies. 228 | - **Graphics**: Each component (`Player`, `Map`, `Enemy`) has its own draw method to handle rendering. 229 | - **Expansion**: This basic setup can be expanded with: 230 | - Player movement using keyboard input. 231 | - Collision detection between the player and map/enemies. 232 | - Basic AI for enemy movement or behavior. 233 | - Inventory system for items. 234 | - Combat mechanics or interaction with the environment. 235 | 236 | #### Conclusion 237 | 238 | You've now built a foundational 2D RPG game renderer in Rust! This project introduces you to game development concepts, graphics handling with `ggez`, and modular code organization. 239 | 240 | Feel free to expand upon this base by: 241 | - Implementing player input for movement. 242 | - Adding different types of tiles or interactive objects. 243 | - Creating a combat system or quests. 244 | - Enhancing the game with sound or more complex graphics. 245 | 246 | This project serves as an excellent platform for learning advanced Rust programming and game development principles. Keep exploring, and have fun creating your own game worlds! -------------------------------------------------------------------------------- /rust-2d-rpg/src/assets/enemy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dexter-xD/7Days7RustProjects/2fb6f1f82edc8c5914d53e72dd09d67aacf354b8/rust-2d-rpg/src/assets/enemy.png -------------------------------------------------------------------------------- /rust-2d-rpg/src/assets/player.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dexter-xD/7Days7RustProjects/2fb6f1f82edc8c5914d53e72dd09d67aacf354b8/rust-2d-rpg/src/assets/player.png -------------------------------------------------------------------------------- /rust-2d-rpg/src/assets/tile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dexter-xD/7Days7RustProjects/2fb6f1f82edc8c5914d53e72dd09d67aacf354b8/rust-2d-rpg/src/assets/tile.png -------------------------------------------------------------------------------- /rust-2d-rpg/src/enemy.rs: -------------------------------------------------------------------------------- 1 | use ggez::graphics; 2 | use ggez::nalgebra as na; 3 | use ggez::{Context, GameResult}; 4 | 5 | pub struct Enemy { 6 | position: na::Point2, 7 | } 8 | 9 | impl Enemy { 10 | pub fn new(ctx: &mut Context) -> GameResult { 11 | Ok(Enemy { 12 | position: na::Point2::new(200.0, 200.0), 13 | }) 14 | } 15 | 16 | pub fn draw(&self, ctx: &mut Context) -> GameResult { 17 | let rectangle = graphics::Mesh::new_rectangle( 18 | ctx, 19 | graphics::DrawMode::fill(), 20 | graphics::Rect::new(self.position.x, self.position.y, 32.0, 32.0), 21 | graphics::Color::new(1.0, 0.0, 0.0, 1.0), 22 | )?; 23 | graphics::draw(ctx, &rectangle, (na::Point2::new(0.0, 0.0),)) 24 | } 25 | 26 | // Add methods for enemy movement, AI, etc. 27 | } 28 | -------------------------------------------------------------------------------- /rust-2d-rpg/src/game.rs: -------------------------------------------------------------------------------- 1 | use ggez::event::{self, EventHandler}; 2 | use ggez::graphics; 3 | use ggez::{Context, GameResult}; 4 | 5 | pub struct Game { 6 | player: player::Player, 7 | map: map::Map, 8 | enemies: Vec, 9 | } 10 | 11 | impl Game { 12 | pub fn new(ctx: &mut Context) -> GameResult { 13 | let player = player::Player::new(ctx)?; 14 | let map = map::Map::new(); 15 | let enemies = vec![enemy::Enemy::new(ctx)?]; 16 | Ok(Game { 17 | player, 18 | map, 19 | enemies, 20 | }) 21 | } 22 | } 23 | 24 | impl EventHandler for Game { 25 | fn update(&mut self, _ctx: &mut Context) -> GameResult { 26 | // Update logic for player movement, enemy behavior, etc. 27 | Ok(()) 28 | } 29 | 30 | fn draw(&mut self, ctx: &mut Context) -> GameResult { 31 | graphics::clear(ctx, [0.1, 0.2, 0.3, 1.0].into()); 32 | self.map.draw(ctx)?; 33 | self.player.draw(ctx)?; 34 | for enemy in &self.enemies { 35 | enemy.draw(ctx)?; 36 | } 37 | graphics::present(ctx)?; 38 | Ok(()) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /rust-2d-rpg/src/main.rs: -------------------------------------------------------------------------------- 1 | mod enemy; 2 | mod game; 3 | mod map; 4 | mod player; 5 | 6 | use ggez::event::{self, EventHandler}; 7 | use ggez::{Context, ContextBuilder, GameResult}; 8 | 9 | fn main() -> GameResult { 10 | let cb = ContextBuilder::new("rust_2d_rpg", "YourName") 11 | .window_setup(ggez::conf::WindowSetup::default().title("2D RPG in Rust")); 12 | let (mut ctx, event_loop) = cb.build()?; 13 | let game = game::Game::new(&mut ctx)?; 14 | event::run(ctx, event_loop, game) 15 | } 16 | -------------------------------------------------------------------------------- /rust-2d-rpg/src/map.rs: -------------------------------------------------------------------------------- 1 | use ggez::graphics; 2 | use ggez::{Context, GameResult}; 3 | 4 | pub struct Map { 5 | // Simplified map representation 6 | tiles: Vec>, 7 | } 8 | 9 | impl Map { 10 | pub fn new() -> Map { 11 | let tiles = vec![vec![true; 20]; 15]; 12 | Map { tiles } 13 | } 14 | 15 | pub fn draw(&self, ctx: &mut Context) -> GameResult { 16 | let tile_size = 32.0; 17 | for (y, row) in self.tiles.iter().enumerate() { 18 | for (x, &tile) in row.iter().enumerate() { 19 | if tile { 20 | let rectangle = graphics::Mesh::new_rectangle( 21 | ctx, 22 | graphics::DrawMode::fill(), 23 | graphics::Rect::new( 24 | (x as f32) * tile_size, 25 | (y as f32) * tile_size, 26 | tile_size, 27 | tile_size, 28 | ), 29 | [0.5, 0.5, 0.5, 1.0].into(), 30 | )?; 31 | graphics::draw(ctx, &rectangle, (na::Point2::new(0.0, 0.0),))?; 32 | } 33 | } 34 | } 35 | Ok(()) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /rust-2d-rpg/src/player.rs: -------------------------------------------------------------------------------- 1 | use ggez::graphics::{self, Rect}; 2 | use ggez::nalgebra as na; 3 | use ggez::{Context, GameResult}; 4 | 5 | pub struct Player { 6 | position: na::Point2, 7 | } 8 | 9 | impl Player { 10 | pub fn new(_ctx: &mut Context) -> GameResult { 11 | Ok(Player { 12 | position: na::Point2::new(100.0, 100.0), 13 | }) 14 | } 15 | 16 | pub fn draw(&self, ctx: &mut Context) -> GameResult { 17 | let rectangle = graphics::Mesh::new_rectangle( 18 | ctx, 19 | graphics::DrawMode::fill(), 20 | Rect::new(self.position.x, self.position.y, 32.0, 32.0), 21 | graphics::Color::new(0.0, 1.0, 0.0, 1.0), 22 | )?; 23 | graphics::draw(ctx, &rectangle, (na::Point2::new(0.0, 0.0),)) 24 | } 25 | 26 | // Add methods for movement, collision detection, etc. 27 | } 28 | -------------------------------------------------------------------------------- /rust-file-encryptor/.gitignore: -------------------------------------------------------------------------------- 1 | /target -------------------------------------------------------------------------------- /rust-file-encryptor/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", 12 | ] 13 | 14 | [[package]] 15 | name = "atty" 16 | version = "0.2.14" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 19 | dependencies = [ 20 | "hermit-abi", 21 | "libc", 22 | "winapi", 23 | ] 24 | 25 | [[package]] 26 | name = "bitflags" 27 | version = "1.3.2" 28 | source = "registry+https://github.com/rust-lang/crates.io-index" 29 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 30 | 31 | [[package]] 32 | name = "clap" 33 | version = "2.34.0" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" 36 | dependencies = [ 37 | "ansi_term", 38 | "atty", 39 | "bitflags", 40 | "strsim", 41 | "textwrap", 42 | "unicode-width", 43 | "vec_map", 44 | ] 45 | 46 | [[package]] 47 | name = "heck" 48 | version = "0.3.3" 49 | source = "registry+https://github.com/rust-lang/crates.io-index" 50 | checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" 51 | dependencies = [ 52 | "unicode-segmentation", 53 | ] 54 | 55 | [[package]] 56 | name = "hermit-abi" 57 | version = "0.1.19" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 60 | dependencies = [ 61 | "libc", 62 | ] 63 | 64 | [[package]] 65 | name = "lazy_static" 66 | version = "1.5.0" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 69 | 70 | [[package]] 71 | name = "libc" 72 | version = "0.2.161" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" 75 | 76 | [[package]] 77 | name = "proc-macro-error" 78 | version = "1.0.4" 79 | source = "registry+https://github.com/rust-lang/crates.io-index" 80 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 81 | dependencies = [ 82 | "proc-macro-error-attr", 83 | "proc-macro2", 84 | "quote", 85 | "syn", 86 | "version_check", 87 | ] 88 | 89 | [[package]] 90 | name = "proc-macro-error-attr" 91 | version = "1.0.4" 92 | source = "registry+https://github.com/rust-lang/crates.io-index" 93 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 94 | dependencies = [ 95 | "proc-macro2", 96 | "quote", 97 | "version_check", 98 | ] 99 | 100 | [[package]] 101 | name = "proc-macro2" 102 | version = "1.0.89" 103 | source = "registry+https://github.com/rust-lang/crates.io-index" 104 | checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" 105 | dependencies = [ 106 | "unicode-ident", 107 | ] 108 | 109 | [[package]] 110 | name = "quote" 111 | version = "1.0.37" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 114 | dependencies = [ 115 | "proc-macro2", 116 | ] 117 | 118 | [[package]] 119 | name = "rust-file-encryptor" 120 | version = "0.1.0" 121 | dependencies = [ 122 | "structopt", 123 | ] 124 | 125 | [[package]] 126 | name = "strsim" 127 | version = "0.8.0" 128 | source = "registry+https://github.com/rust-lang/crates.io-index" 129 | checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 130 | 131 | [[package]] 132 | name = "structopt" 133 | version = "0.3.26" 134 | source = "registry+https://github.com/rust-lang/crates.io-index" 135 | checksum = "0c6b5c64445ba8094a6ab0c3cd2ad323e07171012d9c98b0b15651daf1787a10" 136 | dependencies = [ 137 | "clap", 138 | "lazy_static", 139 | "structopt-derive", 140 | ] 141 | 142 | [[package]] 143 | name = "structopt-derive" 144 | version = "0.4.18" 145 | source = "registry+https://github.com/rust-lang/crates.io-index" 146 | checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" 147 | dependencies = [ 148 | "heck", 149 | "proc-macro-error", 150 | "proc-macro2", 151 | "quote", 152 | "syn", 153 | ] 154 | 155 | [[package]] 156 | name = "syn" 157 | version = "1.0.109" 158 | source = "registry+https://github.com/rust-lang/crates.io-index" 159 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 160 | dependencies = [ 161 | "proc-macro2", 162 | "quote", 163 | "unicode-ident", 164 | ] 165 | 166 | [[package]] 167 | name = "textwrap" 168 | version = "0.11.0" 169 | source = "registry+https://github.com/rust-lang/crates.io-index" 170 | checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 171 | dependencies = [ 172 | "unicode-width", 173 | ] 174 | 175 | [[package]] 176 | name = "unicode-ident" 177 | version = "1.0.13" 178 | source = "registry+https://github.com/rust-lang/crates.io-index" 179 | checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" 180 | 181 | [[package]] 182 | name = "unicode-segmentation" 183 | version = "1.12.0" 184 | source = "registry+https://github.com/rust-lang/crates.io-index" 185 | checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" 186 | 187 | [[package]] 188 | name = "unicode-width" 189 | version = "0.1.14" 190 | source = "registry+https://github.com/rust-lang/crates.io-index" 191 | checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" 192 | 193 | [[package]] 194 | name = "vec_map" 195 | version = "0.8.2" 196 | source = "registry+https://github.com/rust-lang/crates.io-index" 197 | checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" 198 | 199 | [[package]] 200 | name = "version_check" 201 | version = "0.9.5" 202 | source = "registry+https://github.com/rust-lang/crates.io-index" 203 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 204 | 205 | [[package]] 206 | name = "winapi" 207 | version = "0.3.9" 208 | source = "registry+https://github.com/rust-lang/crates.io-index" 209 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 210 | dependencies = [ 211 | "winapi-i686-pc-windows-gnu", 212 | "winapi-x86_64-pc-windows-gnu", 213 | ] 214 | 215 | [[package]] 216 | name = "winapi-i686-pc-windows-gnu" 217 | version = "0.4.0" 218 | source = "registry+https://github.com/rust-lang/crates.io-index" 219 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 220 | 221 | [[package]] 222 | name = "winapi-x86_64-pc-windows-gnu" 223 | version = "0.4.0" 224 | source = "registry+https://github.com/rust-lang/crates.io-index" 225 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 226 | -------------------------------------------------------------------------------- /rust-file-encryptor/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rust-file-encryptor" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | structopt = "0.3" -------------------------------------------------------------------------------- /rust-file-encryptor/README.md: -------------------------------------------------------------------------------- 1 | #### Introduction 2 | Today, we're diving into the world of security with Rust by creating a simple file encryptor/decryptor. We'll use a basic XOR cipher for encryption, but remember, this is for educational purposes; **never use this for actual secure data** due to its simplicity. 3 | 4 | #### Prerequisites 5 | - Basic understanding of Rust 6 | - Familiarity with file I/O operations 7 | - Concept of encryption (even if basic) 8 | 9 | #### Project Structure 10 | Let's set up our project first: 11 | 12 | ```sh 13 | mkdir rust-file-encryptor 14 | cd rust-file-encryptor 15 | cargo init --bin 16 | ``` 17 | 18 | Now, let’s define our folder structure: 19 | 20 | ``` 21 | rust-file-encryptor/ 22 | │ 23 | ├── src/ 24 | │ ├── main.rs 25 | │ ├── cli.rs 26 | │ ├── encryption.rs 27 | │ └── io.rs 28 | │ 29 | ├── Cargo.toml 30 | └── README.md 31 | ``` 32 | 33 | #### Step 1: Setting up `Cargo.toml` 34 | 35 | ```toml 36 | [package] 37 | name = "file_encryptor" 38 | version = "0.1.0" 39 | edition = "2018" 40 | 41 | [dependencies] 42 | structopt = "0.3" 43 | ``` 44 | 45 | #### Step 2: `cli.rs` - Handling Command Line Arguments 46 | 47 | ```rust 48 | use structopt::StructOpt; 49 | 50 | #[derive(StructOpt)] 51 | struct Cli { 52 | #[structopt(parse(from_os_str))] 53 | input: std::path::PathBuf, 54 | #[structopt(short, long)] 55 | decrypt: bool, 56 | } 57 | 58 | pub fn parse_args() -> Cli { 59 | Cli::from_args() 60 | } 61 | ``` 62 | 63 | #### Step 3: `encryption.rs` - Encryption Logic 64 | 65 | ```rust 66 | pub fn encrypt_decrypt(data: &mut [u8], key: u8) { 67 | data.iter_mut().for_each(|byte| *byte ^= key); 68 | } 69 | 70 | pub fn get_key() -> u8 { 71 | 42 // A simple key for demonstration 72 | } 73 | ``` 74 | 75 | #### Step 4: `io.rs` - File Operations 76 | 77 | ```rust 78 | use std::fs; 79 | use std::io::{Read, Write}; 80 | 81 | pub fn read_file(path: &std::path::Path) -> Result, std::io::Error> { 82 | let mut file = fs::File::open(path)?; 83 | let mut buffer = Vec::new(); 84 | file.read_to_end(&mut buffer)?; 85 | Ok(buffer) 86 | } 87 | 88 | pub fn write_file(path: &std::path::Path, data: &[u8]) -> Result<(), std::io::Error> { 89 | let mut file = fs::File::create(path)?; 90 | file.write_all(data)?; 91 | Ok(()) 92 | } 93 | ``` 94 | 95 | #### Step 5: `main.rs` - Tying It All Together 96 | 97 | ```rust 98 | mod cli; 99 | mod encryption; 100 | mod io; 101 | 102 | use std::path::Path; 103 | 104 | fn main() -> Result<(), Box> { 105 | let args = cli::parse_args(); 106 | let key = encryption::get_key(); 107 | let mut data = io::read_file(&args.input)?; 108 | 109 | if args.decrypt { 110 | encryption::encrypt_decrypt(&mut data, key); 111 | let output_path = args.input.with_extension("decrypted"); 112 | io::write_file(&output_path, &data)?; 113 | println!("Decrypted file saved to: {:?}", output_path); 114 | } else { 115 | encryption::encrypt_decrypt(&mut data, key); 116 | let output_path = args.input.with_extension("encrypted"); 117 | io::write_file(&output_path, &data)?; 118 | println!("Encrypted file saved to: {:?}", output_path); 119 | } 120 | 121 | Ok(()) 122 | } 123 | ``` 124 | 125 | #### Step 6: Usage 126 | 127 | To run your encryptor: 128 | 129 | ```sh 130 | cargo run -- --input /path/to/file.txt 131 | cargo run -- --input /path/to/file.txt --decrypt 132 | ``` 133 | 134 | #### Explanation 135 | 136 | - **CLI Parsing**: We use `structopt` to handle command-line arguments, allowing users to specify files for encryption or decryption. 137 | - **Encryption Logic**: We've used a simple XOR operation for demonstrating encryption. XOR with the same key twice will decrypt the data, making this method symmetric. 138 | - **File I/O**: The `io` module handles reading from and writing to files, ensuring we deal with file operations gracefully. 139 | - **Main**: Our main function reads the file, applies encryption or decryption based on the user's choice, and then writes the result back to a new file. 140 | 141 | #### Conclusion 142 | 143 | This project not only introduces you to basic encryption concepts in Rust but also teaches you about file handling, command-line interfaces, and modular programming. Remember, this is a stepping stone towards understanding more complex encryption algorithms like AES or RSA in future projects. 144 | 145 | Feel free to extend this project by: 146 | - Implementing more secure encryption methods. 147 | - Adding error handling for common issues like file not found or permission denied. 148 | - Creating a GUI interface using something like `egui` for a visual component to the encryptor/decryptor. 149 | 150 | This step-by-step guide should have you encrypting and decrypting files with Rust in no time! -------------------------------------------------------------------------------- /rust-file-encryptor/src/cli.rs: -------------------------------------------------------------------------------- 1 | use structopt::StructOpt; 2 | 3 | #[derive(StructOpt)] 4 | struct Cli { 5 | #[structopt(parse(from_os_str))] 6 | input: std::path::PathBuf, 7 | #[structopt(short, long)] 8 | decrypt: bool, 9 | } 10 | 11 | pub fn parse_args() -> Cli { 12 | Cli::from_args() 13 | } 14 | -------------------------------------------------------------------------------- /rust-file-encryptor/src/encryption.rs: -------------------------------------------------------------------------------- 1 | pub fn encrypt_decrypt(data: &mut [u8], key: u8) { 2 | data.iter_mut().for_each(|byte| *byte ^= key); 3 | } 4 | 5 | pub fn get_key() -> u8 { 6 | 42 // A simple key for demonstration 7 | } 8 | -------------------------------------------------------------------------------- /rust-file-encryptor/src/io.rs: -------------------------------------------------------------------------------- 1 | use std::fs; 2 | use std::io::{Read, Write}; 3 | 4 | pub fn read_file(path: &std::path::Path) -> Result, std::io::Error> { 5 | let mut file = fs::File::open(path)?; 6 | let mut buffer = Vec::new(); 7 | file.read_to_end(&mut buffer)?; 8 | Ok(buffer) 9 | } 10 | 11 | pub fn write_file(path: &std::path::Path, data: &[u8]) -> Result<(), std::io::Error> { 12 | let mut file = fs::File::create(path)?; 13 | file.write_all(data)?; 14 | Ok(()) 15 | } 16 | -------------------------------------------------------------------------------- /rust-file-encryptor/src/main.rs: -------------------------------------------------------------------------------- 1 | mod cli; 2 | mod encryption; 3 | mod io; 4 | 5 | use std::path::Path; 6 | 7 | fn main() -> Result<(), Box> { 8 | let args = cli::parse_args(); 9 | let key = encryption::get_key(); 10 | let mut data = io::read_file(&args.input)?; 11 | 12 | if args.decrypt { 13 | encryption::encrypt_decrypt(&mut data, key); 14 | let output_path = args.input.with_extension("decrypted"); 15 | io::write_file(&output_path, &data)?; 16 | println!("Decrypted file saved to: {:?}", output_path); 17 | } else { 18 | encryption::encrypt_decrypt(&mut data, key); 19 | let output_path = args.input.with_extension("encrypted"); 20 | io::write_file(&output_path, &data)?; 21 | println!("Encrypted file saved to: {:?}", output_path); 22 | } 23 | 24 | Ok(()) 25 | } 26 | -------------------------------------------------------------------------------- /rust-tui-chat/.gitignore: -------------------------------------------------------------------------------- 1 | /target -------------------------------------------------------------------------------- /rust-tui-chat/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 = "addr2line" 7 | version = "0.24.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler2" 16 | version = "2.0.0" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" 19 | 20 | [[package]] 21 | name = "anyhow" 22 | version = "1.0.91" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "c042108f3ed77fd83760a5fd79b53be043192bb3b9dba91d8c574c0ada7850c8" 25 | 26 | [[package]] 27 | name = "autocfg" 28 | version = "1.4.0" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 31 | 32 | [[package]] 33 | name = "backtrace" 34 | version = "0.3.74" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" 37 | dependencies = [ 38 | "addr2line", 39 | "cfg-if", 40 | "libc", 41 | "miniz_oxide", 42 | "object", 43 | "rustc-demangle", 44 | "windows-targets", 45 | ] 46 | 47 | [[package]] 48 | name = "bitflags" 49 | version = "1.3.2" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 52 | 53 | [[package]] 54 | name = "bitflags" 55 | version = "2.6.0" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" 58 | 59 | [[package]] 60 | name = "bytes" 61 | version = "1.8.0" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" 64 | 65 | [[package]] 66 | name = "cassowary" 67 | version = "0.3.0" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" 70 | 71 | [[package]] 72 | name = "cfg-if" 73 | version = "1.0.0" 74 | source = "registry+https://github.com/rust-lang/crates.io-index" 75 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 76 | 77 | [[package]] 78 | name = "crossterm" 79 | version = "0.20.0" 80 | source = "registry+https://github.com/rust-lang/crates.io-index" 81 | checksum = "c0ebde6a9dd5e331cd6c6f48253254d117642c31653baa475e394657c59c1f7d" 82 | dependencies = [ 83 | "bitflags 1.3.2", 84 | "crossterm_winapi", 85 | "libc", 86 | "mio 0.7.14", 87 | "parking_lot 0.11.2", 88 | "signal-hook", 89 | "signal-hook-mio", 90 | "winapi", 91 | ] 92 | 93 | [[package]] 94 | name = "crossterm_winapi" 95 | version = "0.8.0" 96 | source = "registry+https://github.com/rust-lang/crates.io-index" 97 | checksum = "3a6966607622438301997d3dac0d2f6e9a90c68bb6bc1785ea98456ab93c0507" 98 | dependencies = [ 99 | "winapi", 100 | ] 101 | 102 | [[package]] 103 | name = "futures-core" 104 | version = "0.3.31" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" 107 | 108 | [[package]] 109 | name = "futures-sink" 110 | version = "0.3.31" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" 113 | 114 | [[package]] 115 | name = "gimli" 116 | version = "0.31.1" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" 119 | 120 | [[package]] 121 | name = "hermit-abi" 122 | version = "0.3.9" 123 | source = "registry+https://github.com/rust-lang/crates.io-index" 124 | checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" 125 | 126 | [[package]] 127 | name = "instant" 128 | version = "0.1.13" 129 | source = "registry+https://github.com/rust-lang/crates.io-index" 130 | checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" 131 | dependencies = [ 132 | "cfg-if", 133 | ] 134 | 135 | [[package]] 136 | name = "libc" 137 | version = "0.2.161" 138 | source = "registry+https://github.com/rust-lang/crates.io-index" 139 | checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" 140 | 141 | [[package]] 142 | name = "lock_api" 143 | version = "0.4.12" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" 146 | dependencies = [ 147 | "autocfg", 148 | "scopeguard", 149 | ] 150 | 151 | [[package]] 152 | name = "log" 153 | version = "0.4.22" 154 | source = "registry+https://github.com/rust-lang/crates.io-index" 155 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" 156 | 157 | [[package]] 158 | name = "memchr" 159 | version = "2.7.4" 160 | source = "registry+https://github.com/rust-lang/crates.io-index" 161 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 162 | 163 | [[package]] 164 | name = "miniz_oxide" 165 | version = "0.8.0" 166 | source = "registry+https://github.com/rust-lang/crates.io-index" 167 | checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" 168 | dependencies = [ 169 | "adler2", 170 | ] 171 | 172 | [[package]] 173 | name = "mio" 174 | version = "0.7.14" 175 | source = "registry+https://github.com/rust-lang/crates.io-index" 176 | checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc" 177 | dependencies = [ 178 | "libc", 179 | "log", 180 | "miow", 181 | "ntapi", 182 | "winapi", 183 | ] 184 | 185 | [[package]] 186 | name = "mio" 187 | version = "1.0.2" 188 | source = "registry+https://github.com/rust-lang/crates.io-index" 189 | checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" 190 | dependencies = [ 191 | "hermit-abi", 192 | "libc", 193 | "wasi", 194 | "windows-sys", 195 | ] 196 | 197 | [[package]] 198 | name = "miow" 199 | version = "0.3.7" 200 | source = "registry+https://github.com/rust-lang/crates.io-index" 201 | checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" 202 | dependencies = [ 203 | "winapi", 204 | ] 205 | 206 | [[package]] 207 | name = "ntapi" 208 | version = "0.3.7" 209 | source = "registry+https://github.com/rust-lang/crates.io-index" 210 | checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f" 211 | dependencies = [ 212 | "winapi", 213 | ] 214 | 215 | [[package]] 216 | name = "object" 217 | version = "0.36.5" 218 | source = "registry+https://github.com/rust-lang/crates.io-index" 219 | checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" 220 | dependencies = [ 221 | "memchr", 222 | ] 223 | 224 | [[package]] 225 | name = "parking_lot" 226 | version = "0.11.2" 227 | source = "registry+https://github.com/rust-lang/crates.io-index" 228 | checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" 229 | dependencies = [ 230 | "instant", 231 | "lock_api", 232 | "parking_lot_core 0.8.6", 233 | ] 234 | 235 | [[package]] 236 | name = "parking_lot" 237 | version = "0.12.3" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" 240 | dependencies = [ 241 | "lock_api", 242 | "parking_lot_core 0.9.10", 243 | ] 244 | 245 | [[package]] 246 | name = "parking_lot_core" 247 | version = "0.8.6" 248 | source = "registry+https://github.com/rust-lang/crates.io-index" 249 | checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" 250 | dependencies = [ 251 | "cfg-if", 252 | "instant", 253 | "libc", 254 | "redox_syscall 0.2.16", 255 | "smallvec", 256 | "winapi", 257 | ] 258 | 259 | [[package]] 260 | name = "parking_lot_core" 261 | version = "0.9.10" 262 | source = "registry+https://github.com/rust-lang/crates.io-index" 263 | checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" 264 | dependencies = [ 265 | "cfg-if", 266 | "libc", 267 | "redox_syscall 0.5.7", 268 | "smallvec", 269 | "windows-targets", 270 | ] 271 | 272 | [[package]] 273 | name = "pin-project-lite" 274 | version = "0.2.15" 275 | source = "registry+https://github.com/rust-lang/crates.io-index" 276 | checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" 277 | 278 | [[package]] 279 | name = "proc-macro2" 280 | version = "1.0.89" 281 | source = "registry+https://github.com/rust-lang/crates.io-index" 282 | checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" 283 | dependencies = [ 284 | "unicode-ident", 285 | ] 286 | 287 | [[package]] 288 | name = "quote" 289 | version = "1.0.37" 290 | source = "registry+https://github.com/rust-lang/crates.io-index" 291 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 292 | dependencies = [ 293 | "proc-macro2", 294 | ] 295 | 296 | [[package]] 297 | name = "redox_syscall" 298 | version = "0.2.16" 299 | source = "registry+https://github.com/rust-lang/crates.io-index" 300 | checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" 301 | dependencies = [ 302 | "bitflags 1.3.2", 303 | ] 304 | 305 | [[package]] 306 | name = "redox_syscall" 307 | version = "0.5.7" 308 | source = "registry+https://github.com/rust-lang/crates.io-index" 309 | checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" 310 | dependencies = [ 311 | "bitflags 2.6.0", 312 | ] 313 | 314 | [[package]] 315 | name = "rust-tui-chat" 316 | version = "0.1.0" 317 | dependencies = [ 318 | "anyhow", 319 | "crossterm", 320 | "tokio", 321 | "tokio-util", 322 | "tui", 323 | ] 324 | 325 | [[package]] 326 | name = "rustc-demangle" 327 | version = "0.1.24" 328 | source = "registry+https://github.com/rust-lang/crates.io-index" 329 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 330 | 331 | [[package]] 332 | name = "scopeguard" 333 | version = "1.2.0" 334 | source = "registry+https://github.com/rust-lang/crates.io-index" 335 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 336 | 337 | [[package]] 338 | name = "signal-hook" 339 | version = "0.3.17" 340 | source = "registry+https://github.com/rust-lang/crates.io-index" 341 | checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" 342 | dependencies = [ 343 | "libc", 344 | "signal-hook-registry", 345 | ] 346 | 347 | [[package]] 348 | name = "signal-hook-mio" 349 | version = "0.2.4" 350 | source = "registry+https://github.com/rust-lang/crates.io-index" 351 | checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" 352 | dependencies = [ 353 | "libc", 354 | "mio 0.7.14", 355 | "signal-hook", 356 | ] 357 | 358 | [[package]] 359 | name = "signal-hook-registry" 360 | version = "1.4.2" 361 | source = "registry+https://github.com/rust-lang/crates.io-index" 362 | checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" 363 | dependencies = [ 364 | "libc", 365 | ] 366 | 367 | [[package]] 368 | name = "smallvec" 369 | version = "1.13.2" 370 | source = "registry+https://github.com/rust-lang/crates.io-index" 371 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 372 | 373 | [[package]] 374 | name = "socket2" 375 | version = "0.5.7" 376 | source = "registry+https://github.com/rust-lang/crates.io-index" 377 | checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" 378 | dependencies = [ 379 | "libc", 380 | "windows-sys", 381 | ] 382 | 383 | [[package]] 384 | name = "syn" 385 | version = "2.0.85" 386 | source = "registry+https://github.com/rust-lang/crates.io-index" 387 | checksum = "5023162dfcd14ef8f32034d8bcd4cc5ddc61ef7a247c024a33e24e1f24d21b56" 388 | dependencies = [ 389 | "proc-macro2", 390 | "quote", 391 | "unicode-ident", 392 | ] 393 | 394 | [[package]] 395 | name = "tokio" 396 | version = "1.41.0" 397 | source = "registry+https://github.com/rust-lang/crates.io-index" 398 | checksum = "145f3413504347a2be84393cc8a7d2fb4d863b375909ea59f2158261aa258bbb" 399 | dependencies = [ 400 | "backtrace", 401 | "bytes", 402 | "libc", 403 | "mio 1.0.2", 404 | "parking_lot 0.12.3", 405 | "pin-project-lite", 406 | "signal-hook-registry", 407 | "socket2", 408 | "tokio-macros", 409 | "windows-sys", 410 | ] 411 | 412 | [[package]] 413 | name = "tokio-macros" 414 | version = "2.4.0" 415 | source = "registry+https://github.com/rust-lang/crates.io-index" 416 | checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" 417 | dependencies = [ 418 | "proc-macro2", 419 | "quote", 420 | "syn", 421 | ] 422 | 423 | [[package]] 424 | name = "tokio-util" 425 | version = "0.6.10" 426 | source = "registry+https://github.com/rust-lang/crates.io-index" 427 | checksum = "36943ee01a6d67977dd3f84a5a1d2efeb4ada3a1ae771cadfaa535d9d9fc6507" 428 | dependencies = [ 429 | "bytes", 430 | "futures-core", 431 | "futures-sink", 432 | "log", 433 | "pin-project-lite", 434 | "tokio", 435 | ] 436 | 437 | [[package]] 438 | name = "tui" 439 | version = "0.16.0" 440 | source = "registry+https://github.com/rust-lang/crates.io-index" 441 | checksum = "39c8ce4e27049eed97cfa363a5048b09d995e209994634a0efc26a14ab6c0c23" 442 | dependencies = [ 443 | "bitflags 1.3.2", 444 | "cassowary", 445 | "crossterm", 446 | "unicode-segmentation", 447 | "unicode-width", 448 | ] 449 | 450 | [[package]] 451 | name = "unicode-ident" 452 | version = "1.0.13" 453 | source = "registry+https://github.com/rust-lang/crates.io-index" 454 | checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" 455 | 456 | [[package]] 457 | name = "unicode-segmentation" 458 | version = "1.12.0" 459 | source = "registry+https://github.com/rust-lang/crates.io-index" 460 | checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" 461 | 462 | [[package]] 463 | name = "unicode-width" 464 | version = "0.1.14" 465 | source = "registry+https://github.com/rust-lang/crates.io-index" 466 | checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" 467 | 468 | [[package]] 469 | name = "wasi" 470 | version = "0.11.0+wasi-snapshot-preview1" 471 | source = "registry+https://github.com/rust-lang/crates.io-index" 472 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 473 | 474 | [[package]] 475 | name = "winapi" 476 | version = "0.3.9" 477 | source = "registry+https://github.com/rust-lang/crates.io-index" 478 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 479 | dependencies = [ 480 | "winapi-i686-pc-windows-gnu", 481 | "winapi-x86_64-pc-windows-gnu", 482 | ] 483 | 484 | [[package]] 485 | name = "winapi-i686-pc-windows-gnu" 486 | version = "0.4.0" 487 | source = "registry+https://github.com/rust-lang/crates.io-index" 488 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 489 | 490 | [[package]] 491 | name = "winapi-x86_64-pc-windows-gnu" 492 | version = "0.4.0" 493 | source = "registry+https://github.com/rust-lang/crates.io-index" 494 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 495 | 496 | [[package]] 497 | name = "windows-sys" 498 | version = "0.52.0" 499 | source = "registry+https://github.com/rust-lang/crates.io-index" 500 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 501 | dependencies = [ 502 | "windows-targets", 503 | ] 504 | 505 | [[package]] 506 | name = "windows-targets" 507 | version = "0.52.6" 508 | source = "registry+https://github.com/rust-lang/crates.io-index" 509 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 510 | dependencies = [ 511 | "windows_aarch64_gnullvm", 512 | "windows_aarch64_msvc", 513 | "windows_i686_gnu", 514 | "windows_i686_gnullvm", 515 | "windows_i686_msvc", 516 | "windows_x86_64_gnu", 517 | "windows_x86_64_gnullvm", 518 | "windows_x86_64_msvc", 519 | ] 520 | 521 | [[package]] 522 | name = "windows_aarch64_gnullvm" 523 | version = "0.52.6" 524 | source = "registry+https://github.com/rust-lang/crates.io-index" 525 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 526 | 527 | [[package]] 528 | name = "windows_aarch64_msvc" 529 | version = "0.52.6" 530 | source = "registry+https://github.com/rust-lang/crates.io-index" 531 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 532 | 533 | [[package]] 534 | name = "windows_i686_gnu" 535 | version = "0.52.6" 536 | source = "registry+https://github.com/rust-lang/crates.io-index" 537 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 538 | 539 | [[package]] 540 | name = "windows_i686_gnullvm" 541 | version = "0.52.6" 542 | source = "registry+https://github.com/rust-lang/crates.io-index" 543 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 544 | 545 | [[package]] 546 | name = "windows_i686_msvc" 547 | version = "0.52.6" 548 | source = "registry+https://github.com/rust-lang/crates.io-index" 549 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 550 | 551 | [[package]] 552 | name = "windows_x86_64_gnu" 553 | version = "0.52.6" 554 | source = "registry+https://github.com/rust-lang/crates.io-index" 555 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 556 | 557 | [[package]] 558 | name = "windows_x86_64_gnullvm" 559 | version = "0.52.6" 560 | source = "registry+https://github.com/rust-lang/crates.io-index" 561 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 562 | 563 | [[package]] 564 | name = "windows_x86_64_msvc" 565 | version = "0.52.6" 566 | source = "registry+https://github.com/rust-lang/crates.io-index" 567 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 568 | -------------------------------------------------------------------------------- /rust-tui-chat/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rust-tui-chat" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | crossterm = "0.20" 8 | tokio = { version = "1", features = ["full"] } 9 | tokio-util = { version = "0.6", features = ["codec"] } 10 | tui = { version = "0.16", default-features = false, features = ['crossterm'] } 11 | anyhow = "1.0" -------------------------------------------------------------------------------- /rust-tui-chat/README.md: -------------------------------------------------------------------------------- 1 | ### **Day 6: Rust TUI Chat Application - Mastering Terminal User Interfaces** 2 | 3 | #### Overview 4 | Today, we're going to build a chat application using Rust's terminal user interface (TUI) libraries. This application will allow two instances to communicate over a network, simulating a simple chat. We'll use `tui` for the interface and `tokio` for asynchronous networking. 5 | 6 | #### Difficulty 7 | 🌲 **Advanced** 8 | 9 | #### Prerequisites 10 | - Intermediate Rust knowledge 11 | - Familiarity with asynchronous programming 12 | - Basic understanding of networking in Rust 13 | 14 | #### Project Structure 15 | 16 | First, let's set up our project: 17 | 18 | ```sh 19 | mkdir rust-tui-chat 20 | cd rust-tui-chat 21 | cargo init --bin 22 | ``` 23 | 24 | Now, let’s define our folder structure: 25 | 26 | ``` 27 | rust-tui-chat/ 28 | │ 29 | ├── src/ 30 | │ ├── main.rs 31 | │ ├── ui.rs 32 | │ ├── network.rs 33 | │ └── message.rs 34 | │ 35 | ├── Cargo.toml 36 | └── README.md 37 | ``` 38 | 39 | #### Step 1: Setting up `Cargo.toml` 40 | 41 | ```toml 42 | [package] 43 | name = "rust_tui_chat" 44 | version = "0.1.0" 45 | edition = "2018" 46 | 47 | [dependencies] 48 | crossterm = "0.20" 49 | tokio = { version = "1", features = ["full"] } 50 | tokio-util = { version = "0.6", features = ["codec"] } 51 | tui = { version = "0.16", default-features = false, features = ['crossterm'] } 52 | anyhow = "1.0" 53 | ``` 54 | 55 | #### Step 2: `ui.rs` - Terminal User Interface 56 | 57 | ```rust 58 | use tui::{ 59 | backend::CrosstermBackend, 60 | layout::{Constraint, Direction, Layout}, 61 | widgets::{Block, Borders, Paragraph}, 62 | Terminal, 63 | }; 64 | use crossterm::{ 65 | event::{self, DisableMouseCapture, Event, KeyCode}, 66 | execute, 67 | terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, 68 | }; 69 | 70 | pub struct Ui { 71 | terminal: Terminal>, 72 | } 73 | 74 | impl Ui { 75 | pub fn new() -> Result { 76 | enable_raw_mode()?; 77 | let mut stdout = std::io::stdout(); 78 | execute!(stdout, EnterAlternateScreen)?; 79 | let backend = CrosstermBackend::new(stdout); 80 | let terminal = Terminal::new(backend)?; 81 | Ok(Self { terminal }) 82 | } 83 | 84 | pub fn draw(&mut self, input: &str, messages: &[String]) -> Result<(), anyhow::Error> { 85 | self.terminal.draw(|f| { 86 | let chunks = Layout::default() 87 | .direction(Direction::Vertical) 88 | .margin(1) 89 | .constraints([Constraint::Percentage(80), Constraint::Percentage(20)].as_ref()) 90 | .split(f.size()); 91 | let messages_block = Block::default().title("Chat").borders(Borders::ALL); 92 | let input_block = Block::default().title("Input").borders(Borders::ALL); 93 | f.render_widget(messages_block, chunks[0]); 94 | f.render_widget(Paragraph::new(input), chunks[1]); 95 | })?; 96 | Ok(()) 97 | } 98 | 99 | pub fn cleanup(&mut self) -> Result<(), anyhow::Error> { 100 | disable_raw_mode()?; 101 | execute!(self.terminal.backend_mut(), LeaveAlternateScreen, DisableMouseCapture)?; 102 | self.terminal.show_cursor()?; 103 | Ok(()) 104 | } 105 | } 106 | ``` 107 | 108 | #### Step 3: `network.rs` - Network Communication 109 | 110 | ```rust 111 | use tokio::{ 112 | net::{TcpListener, TcpStream}, 113 | io::{AsyncReadExt, AsyncWriteExt}, 114 | }; 115 | 116 | pub struct Network { 117 | stream: Option, 118 | } 119 | 120 | impl Network { 121 | pub async fn new(addr: &str) -> Result { 122 | let stream = if addr.contains(":") { 123 | TcpStream::connect(addr).await? 124 | } else { 125 | let listener = TcpListener::bind(addr).await?; 126 | let (stream, _) = listener.accept().await?; 127 | stream 128 | }; 129 | Ok(Self { stream: Some(stream) }) 130 | } 131 | 132 | pub async fn send_message(&mut self, msg: &str) -> Result<(), anyhow::Error> { 133 | if let Some(stream) = self.stream.as_mut() { 134 | stream.write_all(&[msg.len() as u8]).await?; 135 | stream.write_all(msg.as_bytes()).await?; 136 | } 137 | Ok(()) 138 | } 139 | 140 | pub async fn receive_message(&mut self) -> Result { 141 | if let Some(stream) = self.stream.as_mut() { 142 | let mut len = [0u8; 1]; 143 | stream.read_exact(&mut len).await?; 144 | let mut buffer = vec![0; len[0] as usize]; 145 | stream.read_exact(&mut buffer).await?; 146 | return String::from_utf8(buffer).map_err(|e| e.into()); 147 | } 148 | Err(anyhow::anyhow!("No stream available")) 149 | } 150 | } 151 | ``` 152 | 153 | #### Step 4: `message.rs` - Message Handling 154 | 155 | ```rust 156 | pub struct Message { 157 | pub text: String, 158 | } 159 | 160 | impl Message { 161 | pub fn new(text: String) -> Self { 162 | Message { text } 163 | } 164 | } 165 | ``` 166 | 167 | #### Step 5: `main.rs` - Main Application Logic 168 | 169 | ```rust 170 | mod ui; 171 | mod network; 172 | mod message; 173 | 174 | use anyhow::Result; 175 | use std::sync::mpsc; 176 | use std::thread; 177 | use std::time::Duration; 178 | 179 | #[tokio::main] 180 | async fn main() -> Result<()> { 181 | let mut ui = ui::Ui::new()?; 182 | let (tx, rx) = mpsc::channel(); 183 | let mut network = network::Network::new("127.0.0.1:8080").await?; 184 | 185 | let handle = thread::spawn(move || { 186 | let mut input = String::new(); 187 | loop { 188 | if let Ok(Event::Key(key)) = event::read() { 189 | match key.code { 190 | KeyCode::Char(c) => input.push(c), 191 | KeyCode::Enter => { 192 | let _ = tx.send(input.clone()); 193 | input.clear(); 194 | }, 195 | KeyCode::Backspace => { input.pop(); }, 196 | KeyCode::Esc => break, 197 | _ => {}, 198 | } 199 | } 200 | } 201 | }); 202 | 203 | let mut messages = Vec::new(); 204 | loop { 205 | match rx.try_recv() { 206 | Ok(msg) => { 207 | network.send_message(&msg).await?; 208 | messages.push(msg); 209 | }, 210 | Err(_) => { 211 | if let Ok(msg) = network.receive_message().await { 212 | messages.push(msg); 213 | } 214 | }, 215 | } 216 | ui.draw(&input, &messages)?; 217 | thread::sleep(Duration::from_millis(100)); 218 | } 219 | 220 | ui.cleanup()?; 221 | handle.join().unwrap(); 222 | Ok(()) 223 | } 224 | ``` 225 | 226 | #### Step 6: Running Your Chat App 227 | 228 | To run the application: 229 | 230 | ```sh 231 | cargo run 232 | ``` 233 | 234 | #### Explanation 235 | 236 | - **UI**: We've used `tui` for creating a clean, interactive terminal interface where users can see messages and type their own. 237 | - **Networking**: Async networking with `tokio` allows for sending and receiving messages over TCP. Here, you can run one instance as the server and another as the client locally or over a network. 238 | - **Message Handling**: Simple struct to manage messages in our application. 239 | 240 | #### Conclusion 241 | 242 | This project is an advanced step into Rust programming, combining TUI, networking, and asynchronous programming. It opens the door to further enhancements like: 243 | 244 | - Adding user authentication. 245 | - Implementing chat rooms or private messaging. 246 | - Enhancing the UI with more widgets or color. 247 | 248 | This chat application serves as a solid foundation for experimenting with terminal applications and network communication in Rust. -------------------------------------------------------------------------------- /rust-tui-chat/src/main.rs: -------------------------------------------------------------------------------- 1 | mod ui; 2 | mod network; 3 | mod message; 4 | 5 | use anyhow::Result; 6 | use std::sync::mpsc; 7 | use std::thread; 8 | use std::time::Duration; 9 | 10 | #[tokio::main] 11 | async fn main() -> Result<()> { 12 | let mut ui = ui::Ui::new()?; 13 | let (tx, rx) = mpsc::channel(); 14 | let mut network = network::Network::new("127.0.0.1:8080").await?; 15 | 16 | let handle = thread::spawn(move || { 17 | let mut input = String::new(); 18 | loop { 19 | if let Ok(Event::Key(key)) = event::read() { 20 | match key.code { 21 | KeyCode::Char(c) => input.push(c), 22 | KeyCode::Enter => { 23 | let _ = tx.send(input.clone()); 24 | input.clear(); 25 | }, 26 | KeyCode::Backspace => { input.pop(); }, 27 | KeyCode::Esc => break, 28 | _ => {}, 29 | } 30 | } 31 | } 32 | }); 33 | 34 | let mut messages = Vec::new(); 35 | loop { 36 | match rx.try_recv() { 37 | Ok(msg) => { 38 | network.send_message(&msg).await?; 39 | messages.push(msg); 40 | }, 41 | Err(_) => { 42 | if let Ok(msg) = network.receive_message().await { 43 | messages.push(msg); 44 | } 45 | }, 46 | } 47 | ui.draw(&input, &messages)?; 48 | thread::sleep(Duration::from_millis(100)); 49 | } 50 | 51 | ui.cleanup()?; 52 | handle.join().unwrap(); 53 | Ok(()) 54 | } -------------------------------------------------------------------------------- /rust-tui-chat/src/message.rs: -------------------------------------------------------------------------------- 1 | pub struct Message { 2 | pub text: String, 3 | } 4 | 5 | impl Message { 6 | pub fn new(text: String) -> Self { 7 | Message { text } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /rust-tui-chat/src/network.rs: -------------------------------------------------------------------------------- 1 | use tokio::{ 2 | net::{TcpListener, TcpStream}, 3 | io::{AsyncReadExt, AsyncWriteExt}, 4 | }; 5 | 6 | pub struct Network { 7 | stream: Option, 8 | } 9 | 10 | impl Network { 11 | pub async fn new(addr: &str) -> Result { 12 | let stream = if addr.contains(":") { 13 | TcpStream::connect(addr).await? 14 | } else { 15 | let listener = TcpListener::bind(addr).await?; 16 | let (stream, _) = listener.accept().await?; 17 | stream 18 | }; 19 | Ok(Self { stream: Some(stream) }) 20 | } 21 | 22 | pub async fn send_message(&mut self, msg: &str) -> Result<(), anyhow::Error> { 23 | if let Some(stream) = self.stream.as_mut() { 24 | stream.write_all(&[msg.len() as u8]).await?; 25 | stream.write_all(msg.as_bytes()).await?; 26 | } 27 | Ok(()) 28 | } 29 | 30 | pub async fn receive_message(&mut self) -> Result { 31 | if let Some(stream) = self.stream.as_mut() { 32 | let mut len = [0u8; 1]; 33 | stream.read_exact(&mut len).await?; 34 | let mut buffer = vec![0; len[0] as usize]; 35 | stream.read_exact(&mut buffer).await?; 36 | return String::from_utf8(buffer).map_err(|e| e.into()); 37 | } 38 | Err(anyhow::anyhow!("No stream available")) 39 | } 40 | } -------------------------------------------------------------------------------- /rust-tui-chat/src/ui.rs: -------------------------------------------------------------------------------- 1 | use crossterm::{ 2 | event::{self, DisableMouseCapture, Event, KeyCode}, 3 | execute, 4 | terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, 5 | }; 6 | use tui::{ 7 | backend::CrosstermBackend, 8 | layout::{Constraint, Direction, Layout}, 9 | widgets::{Block, Borders, Paragraph}, 10 | Terminal, 11 | }; 12 | 13 | pub struct Ui { 14 | terminal: Terminal>, 15 | } 16 | 17 | impl Ui { 18 | pub fn new() -> Result { 19 | enable_raw_mode()?; 20 | let mut stdout = std::io::stdout(); 21 | execute!(stdout, EnterAlternateScreen)?; 22 | let backend = CrosstermBackend::new(stdout); 23 | let terminal = Terminal::new(backend)?; 24 | Ok(Self { terminal }) 25 | } 26 | 27 | pub fn draw(&mut self, input: &str, messages: &[String]) -> Result<(), anyhow::Error> { 28 | self.terminal.draw(|f| { 29 | let chunks = Layout::default() 30 | .direction(Direction::Vertical) 31 | .margin(1) 32 | .constraints([Constraint::Percentage(80), Constraint::Percentage(20)].as_ref()) 33 | .split(f.size()); 34 | let messages_block = Block::default().title("Chat").borders(Borders::ALL); 35 | let input_block = Block::default().title("Input").borders(Borders::ALL); 36 | f.render_widget(messages_block, chunks[0]); 37 | f.render_widget(Paragraph::new(input), chunks[1]); 38 | })?; 39 | Ok(()) 40 | } 41 | 42 | pub fn cleanup(&mut self) -> Result<(), anyhow::Error> { 43 | disable_raw_mode()?; 44 | execute!( 45 | self.terminal.backend_mut(), 46 | LeaveAlternateScreen, 47 | DisableMouseCapture 48 | )?; 49 | self.terminal.show_cursor()?; 50 | Ok(()) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /rust-web-scraper/.gitignore: -------------------------------------------------------------------------------- 1 | /target -------------------------------------------------------------------------------- /rust-web-scraper/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 = "actix-codec" 7 | version = "0.5.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" 10 | dependencies = [ 11 | "bitflags 2.6.0", 12 | "bytes", 13 | "futures-core", 14 | "futures-sink", 15 | "memchr", 16 | "pin-project-lite", 17 | "tokio", 18 | "tokio-util", 19 | "tracing", 20 | ] 21 | 22 | [[package]] 23 | name = "actix-http" 24 | version = "3.9.0" 25 | source = "registry+https://github.com/rust-lang/crates.io-index" 26 | checksum = "d48f96fc3003717aeb9856ca3d02a8c7de502667ad76eeacd830b48d2e91fac4" 27 | dependencies = [ 28 | "actix-codec", 29 | "actix-rt", 30 | "actix-service", 31 | "actix-utils", 32 | "ahash", 33 | "base64 0.22.1", 34 | "bitflags 2.6.0", 35 | "brotli", 36 | "bytes", 37 | "bytestring", 38 | "derive_more", 39 | "encoding_rs", 40 | "flate2", 41 | "futures-core", 42 | "h2", 43 | "http", 44 | "httparse", 45 | "httpdate", 46 | "itoa 1.0.11", 47 | "language-tags", 48 | "local-channel", 49 | "mime", 50 | "percent-encoding", 51 | "pin-project-lite", 52 | "rand 0.8.5", 53 | "sha1", 54 | "smallvec", 55 | "tokio", 56 | "tokio-util", 57 | "tracing", 58 | "zstd", 59 | ] 60 | 61 | [[package]] 62 | name = "actix-macros" 63 | version = "0.2.4" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" 66 | dependencies = [ 67 | "quote", 68 | "syn 2.0.85", 69 | ] 70 | 71 | [[package]] 72 | name = "actix-router" 73 | version = "0.5.3" 74 | source = "registry+https://github.com/rust-lang/crates.io-index" 75 | checksum = "13d324164c51f63867b57e73ba5936ea151b8a41a1d23d1031eeb9f70d0236f8" 76 | dependencies = [ 77 | "bytestring", 78 | "cfg-if", 79 | "http", 80 | "regex", 81 | "regex-lite", 82 | "serde", 83 | "tracing", 84 | ] 85 | 86 | [[package]] 87 | name = "actix-rt" 88 | version = "2.10.0" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | checksum = "24eda4e2a6e042aa4e55ac438a2ae052d3b5da0ecf83d7411e1a368946925208" 91 | dependencies = [ 92 | "futures-core", 93 | "tokio", 94 | ] 95 | 96 | [[package]] 97 | name = "actix-server" 98 | version = "2.5.0" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | checksum = "7ca2549781d8dd6d75c40cf6b6051260a2cc2f3c62343d761a969a0640646894" 101 | dependencies = [ 102 | "actix-rt", 103 | "actix-service", 104 | "actix-utils", 105 | "futures-core", 106 | "futures-util", 107 | "mio", 108 | "socket2", 109 | "tokio", 110 | "tracing", 111 | ] 112 | 113 | [[package]] 114 | name = "actix-service" 115 | version = "2.0.2" 116 | source = "registry+https://github.com/rust-lang/crates.io-index" 117 | checksum = "3b894941f818cfdc7ccc4b9e60fa7e53b5042a2e8567270f9147d5591893373a" 118 | dependencies = [ 119 | "futures-core", 120 | "paste", 121 | "pin-project-lite", 122 | ] 123 | 124 | [[package]] 125 | name = "actix-utils" 126 | version = "3.0.1" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8" 129 | dependencies = [ 130 | "local-waker", 131 | "pin-project-lite", 132 | ] 133 | 134 | [[package]] 135 | name = "actix-web" 136 | version = "4.9.0" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | checksum = "9180d76e5cc7ccbc4d60a506f2c727730b154010262df5b910eb17dbe4b8cb38" 139 | dependencies = [ 140 | "actix-codec", 141 | "actix-http", 142 | "actix-macros", 143 | "actix-router", 144 | "actix-rt", 145 | "actix-server", 146 | "actix-service", 147 | "actix-utils", 148 | "actix-web-codegen", 149 | "ahash", 150 | "bytes", 151 | "bytestring", 152 | "cfg-if", 153 | "cookie", 154 | "derive_more", 155 | "encoding_rs", 156 | "futures-core", 157 | "futures-util", 158 | "impl-more", 159 | "itoa 1.0.11", 160 | "language-tags", 161 | "log", 162 | "mime", 163 | "once_cell", 164 | "pin-project-lite", 165 | "regex", 166 | "regex-lite", 167 | "serde", 168 | "serde_json", 169 | "serde_urlencoded", 170 | "smallvec", 171 | "socket2", 172 | "time", 173 | "url", 174 | ] 175 | 176 | [[package]] 177 | name = "actix-web-codegen" 178 | version = "4.3.0" 179 | source = "registry+https://github.com/rust-lang/crates.io-index" 180 | checksum = "f591380e2e68490b5dfaf1dd1aa0ebe78d84ba7067078512b4ea6e4492d622b8" 181 | dependencies = [ 182 | "actix-router", 183 | "proc-macro2", 184 | "quote", 185 | "syn 2.0.85", 186 | ] 187 | 188 | [[package]] 189 | name = "addr2line" 190 | version = "0.24.2" 191 | source = "registry+https://github.com/rust-lang/crates.io-index" 192 | checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" 193 | dependencies = [ 194 | "gimli", 195 | ] 196 | 197 | [[package]] 198 | name = "adler2" 199 | version = "2.0.0" 200 | source = "registry+https://github.com/rust-lang/crates.io-index" 201 | checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" 202 | 203 | [[package]] 204 | name = "ahash" 205 | version = "0.8.11" 206 | source = "registry+https://github.com/rust-lang/crates.io-index" 207 | checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" 208 | dependencies = [ 209 | "cfg-if", 210 | "getrandom 0.2.15", 211 | "once_cell", 212 | "version_check", 213 | "zerocopy", 214 | ] 215 | 216 | [[package]] 217 | name = "aho-corasick" 218 | version = "1.1.3" 219 | source = "registry+https://github.com/rust-lang/crates.io-index" 220 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 221 | dependencies = [ 222 | "memchr", 223 | ] 224 | 225 | [[package]] 226 | name = "alloc-no-stdlib" 227 | version = "2.0.4" 228 | source = "registry+https://github.com/rust-lang/crates.io-index" 229 | checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" 230 | 231 | [[package]] 232 | name = "alloc-stdlib" 233 | version = "0.2.2" 234 | source = "registry+https://github.com/rust-lang/crates.io-index" 235 | checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" 236 | dependencies = [ 237 | "alloc-no-stdlib", 238 | ] 239 | 240 | [[package]] 241 | name = "autocfg" 242 | version = "1.4.0" 243 | source = "registry+https://github.com/rust-lang/crates.io-index" 244 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 245 | 246 | [[package]] 247 | name = "backtrace" 248 | version = "0.3.74" 249 | source = "registry+https://github.com/rust-lang/crates.io-index" 250 | checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" 251 | dependencies = [ 252 | "addr2line", 253 | "cfg-if", 254 | "libc", 255 | "miniz_oxide", 256 | "object", 257 | "rustc-demangle", 258 | "windows-targets 0.52.6", 259 | ] 260 | 261 | [[package]] 262 | name = "base64" 263 | version = "0.21.7" 264 | source = "registry+https://github.com/rust-lang/crates.io-index" 265 | checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" 266 | 267 | [[package]] 268 | name = "base64" 269 | version = "0.22.1" 270 | source = "registry+https://github.com/rust-lang/crates.io-index" 271 | checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" 272 | 273 | [[package]] 274 | name = "bitflags" 275 | version = "1.3.2" 276 | source = "registry+https://github.com/rust-lang/crates.io-index" 277 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 278 | 279 | [[package]] 280 | name = "bitflags" 281 | version = "2.6.0" 282 | source = "registry+https://github.com/rust-lang/crates.io-index" 283 | checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" 284 | 285 | [[package]] 286 | name = "block-buffer" 287 | version = "0.10.4" 288 | source = "registry+https://github.com/rust-lang/crates.io-index" 289 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 290 | dependencies = [ 291 | "generic-array", 292 | ] 293 | 294 | [[package]] 295 | name = "brotli" 296 | version = "6.0.0" 297 | source = "registry+https://github.com/rust-lang/crates.io-index" 298 | checksum = "74f7971dbd9326d58187408ab83117d8ac1bb9c17b085fdacd1cf2f598719b6b" 299 | dependencies = [ 300 | "alloc-no-stdlib", 301 | "alloc-stdlib", 302 | "brotli-decompressor", 303 | ] 304 | 305 | [[package]] 306 | name = "brotli-decompressor" 307 | version = "4.0.1" 308 | source = "registry+https://github.com/rust-lang/crates.io-index" 309 | checksum = "9a45bd2e4095a8b518033b128020dd4a55aab1c0a381ba4404a472630f4bc362" 310 | dependencies = [ 311 | "alloc-no-stdlib", 312 | "alloc-stdlib", 313 | ] 314 | 315 | [[package]] 316 | name = "bumpalo" 317 | version = "3.16.0" 318 | source = "registry+https://github.com/rust-lang/crates.io-index" 319 | checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" 320 | 321 | [[package]] 322 | name = "byteorder" 323 | version = "1.5.0" 324 | source = "registry+https://github.com/rust-lang/crates.io-index" 325 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 326 | 327 | [[package]] 328 | name = "bytes" 329 | version = "1.8.0" 330 | source = "registry+https://github.com/rust-lang/crates.io-index" 331 | checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" 332 | 333 | [[package]] 334 | name = "bytestring" 335 | version = "1.3.1" 336 | source = "registry+https://github.com/rust-lang/crates.io-index" 337 | checksum = "74d80203ea6b29df88012294f62733de21cfeab47f17b41af3a38bc30a03ee72" 338 | dependencies = [ 339 | "bytes", 340 | ] 341 | 342 | [[package]] 343 | name = "cc" 344 | version = "1.1.31" 345 | source = "registry+https://github.com/rust-lang/crates.io-index" 346 | checksum = "c2e7962b54006dcfcc61cb72735f4d89bb97061dd6a7ed882ec6b8ee53714c6f" 347 | dependencies = [ 348 | "jobserver", 349 | "libc", 350 | "shlex", 351 | ] 352 | 353 | [[package]] 354 | name = "cfg-if" 355 | version = "1.0.0" 356 | source = "registry+https://github.com/rust-lang/crates.io-index" 357 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 358 | 359 | [[package]] 360 | name = "convert_case" 361 | version = "0.4.0" 362 | source = "registry+https://github.com/rust-lang/crates.io-index" 363 | checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" 364 | 365 | [[package]] 366 | name = "cookie" 367 | version = "0.16.2" 368 | source = "registry+https://github.com/rust-lang/crates.io-index" 369 | checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" 370 | dependencies = [ 371 | "percent-encoding", 372 | "time", 373 | "version_check", 374 | ] 375 | 376 | [[package]] 377 | name = "core-foundation" 378 | version = "0.9.4" 379 | source = "registry+https://github.com/rust-lang/crates.io-index" 380 | checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" 381 | dependencies = [ 382 | "core-foundation-sys", 383 | "libc", 384 | ] 385 | 386 | [[package]] 387 | name = "core-foundation-sys" 388 | version = "0.8.7" 389 | source = "registry+https://github.com/rust-lang/crates.io-index" 390 | checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 391 | 392 | [[package]] 393 | name = "cpufeatures" 394 | version = "0.2.14" 395 | source = "registry+https://github.com/rust-lang/crates.io-index" 396 | checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" 397 | dependencies = [ 398 | "libc", 399 | ] 400 | 401 | [[package]] 402 | name = "crc32fast" 403 | version = "1.4.2" 404 | source = "registry+https://github.com/rust-lang/crates.io-index" 405 | checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" 406 | dependencies = [ 407 | "cfg-if", 408 | ] 409 | 410 | [[package]] 411 | name = "crypto-common" 412 | version = "0.1.6" 413 | source = "registry+https://github.com/rust-lang/crates.io-index" 414 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 415 | dependencies = [ 416 | "generic-array", 417 | "typenum", 418 | ] 419 | 420 | [[package]] 421 | name = "cssparser" 422 | version = "0.27.2" 423 | source = "registry+https://github.com/rust-lang/crates.io-index" 424 | checksum = "754b69d351cdc2d8ee09ae203db831e005560fc6030da058f86ad60c92a9cb0a" 425 | dependencies = [ 426 | "cssparser-macros", 427 | "dtoa-short", 428 | "itoa 0.4.8", 429 | "matches", 430 | "phf", 431 | "proc-macro2", 432 | "quote", 433 | "smallvec", 434 | "syn 1.0.109", 435 | ] 436 | 437 | [[package]] 438 | name = "cssparser-macros" 439 | version = "0.6.1" 440 | source = "registry+https://github.com/rust-lang/crates.io-index" 441 | checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" 442 | dependencies = [ 443 | "quote", 444 | "syn 2.0.85", 445 | ] 446 | 447 | [[package]] 448 | name = "deranged" 449 | version = "0.3.11" 450 | source = "registry+https://github.com/rust-lang/crates.io-index" 451 | checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" 452 | dependencies = [ 453 | "powerfmt", 454 | ] 455 | 456 | [[package]] 457 | name = "derive_more" 458 | version = "0.99.18" 459 | source = "registry+https://github.com/rust-lang/crates.io-index" 460 | checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce" 461 | dependencies = [ 462 | "convert_case", 463 | "proc-macro2", 464 | "quote", 465 | "rustc_version", 466 | "syn 2.0.85", 467 | ] 468 | 469 | [[package]] 470 | name = "digest" 471 | version = "0.10.7" 472 | source = "registry+https://github.com/rust-lang/crates.io-index" 473 | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 474 | dependencies = [ 475 | "block-buffer", 476 | "crypto-common", 477 | ] 478 | 479 | [[package]] 480 | name = "dtoa" 481 | version = "1.0.9" 482 | source = "registry+https://github.com/rust-lang/crates.io-index" 483 | checksum = "dcbb2bf8e87535c23f7a8a321e364ce21462d0ff10cb6407820e8e96dfff6653" 484 | 485 | [[package]] 486 | name = "dtoa-short" 487 | version = "0.3.5" 488 | source = "registry+https://github.com/rust-lang/crates.io-index" 489 | checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" 490 | dependencies = [ 491 | "dtoa", 492 | ] 493 | 494 | [[package]] 495 | name = "ego-tree" 496 | version = "0.6.3" 497 | source = "registry+https://github.com/rust-lang/crates.io-index" 498 | checksum = "12a0bb14ac04a9fcf170d0bbbef949b44cc492f4452bd20c095636956f653642" 499 | 500 | [[package]] 501 | name = "encoding_rs" 502 | version = "0.8.35" 503 | source = "registry+https://github.com/rust-lang/crates.io-index" 504 | checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" 505 | dependencies = [ 506 | "cfg-if", 507 | ] 508 | 509 | [[package]] 510 | name = "equivalent" 511 | version = "1.0.1" 512 | source = "registry+https://github.com/rust-lang/crates.io-index" 513 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 514 | 515 | [[package]] 516 | name = "errno" 517 | version = "0.3.9" 518 | source = "registry+https://github.com/rust-lang/crates.io-index" 519 | checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" 520 | dependencies = [ 521 | "libc", 522 | "windows-sys 0.52.0", 523 | ] 524 | 525 | [[package]] 526 | name = "fastrand" 527 | version = "2.1.1" 528 | source = "registry+https://github.com/rust-lang/crates.io-index" 529 | checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" 530 | 531 | [[package]] 532 | name = "flate2" 533 | version = "1.0.34" 534 | source = "registry+https://github.com/rust-lang/crates.io-index" 535 | checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0" 536 | dependencies = [ 537 | "crc32fast", 538 | "miniz_oxide", 539 | ] 540 | 541 | [[package]] 542 | name = "fnv" 543 | version = "1.0.7" 544 | source = "registry+https://github.com/rust-lang/crates.io-index" 545 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 546 | 547 | [[package]] 548 | name = "foreign-types" 549 | version = "0.3.2" 550 | source = "registry+https://github.com/rust-lang/crates.io-index" 551 | checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 552 | dependencies = [ 553 | "foreign-types-shared", 554 | ] 555 | 556 | [[package]] 557 | name = "foreign-types-shared" 558 | version = "0.1.1" 559 | source = "registry+https://github.com/rust-lang/crates.io-index" 560 | checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 561 | 562 | [[package]] 563 | name = "form_urlencoded" 564 | version = "1.2.1" 565 | source = "registry+https://github.com/rust-lang/crates.io-index" 566 | checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" 567 | dependencies = [ 568 | "percent-encoding", 569 | ] 570 | 571 | [[package]] 572 | name = "futf" 573 | version = "0.1.5" 574 | source = "registry+https://github.com/rust-lang/crates.io-index" 575 | checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" 576 | dependencies = [ 577 | "mac", 578 | "new_debug_unreachable", 579 | ] 580 | 581 | [[package]] 582 | name = "futures-channel" 583 | version = "0.3.31" 584 | source = "registry+https://github.com/rust-lang/crates.io-index" 585 | checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" 586 | dependencies = [ 587 | "futures-core", 588 | ] 589 | 590 | [[package]] 591 | name = "futures-core" 592 | version = "0.3.31" 593 | source = "registry+https://github.com/rust-lang/crates.io-index" 594 | checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" 595 | 596 | [[package]] 597 | name = "futures-sink" 598 | version = "0.3.31" 599 | source = "registry+https://github.com/rust-lang/crates.io-index" 600 | checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" 601 | 602 | [[package]] 603 | name = "futures-task" 604 | version = "0.3.31" 605 | source = "registry+https://github.com/rust-lang/crates.io-index" 606 | checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" 607 | 608 | [[package]] 609 | name = "futures-util" 610 | version = "0.3.31" 611 | source = "registry+https://github.com/rust-lang/crates.io-index" 612 | checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" 613 | dependencies = [ 614 | "futures-core", 615 | "futures-task", 616 | "pin-project-lite", 617 | "pin-utils", 618 | ] 619 | 620 | [[package]] 621 | name = "fxhash" 622 | version = "0.2.1" 623 | source = "registry+https://github.com/rust-lang/crates.io-index" 624 | checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" 625 | dependencies = [ 626 | "byteorder", 627 | ] 628 | 629 | [[package]] 630 | name = "generic-array" 631 | version = "0.14.7" 632 | source = "registry+https://github.com/rust-lang/crates.io-index" 633 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 634 | dependencies = [ 635 | "typenum", 636 | "version_check", 637 | ] 638 | 639 | [[package]] 640 | name = "getopts" 641 | version = "0.2.21" 642 | source = "registry+https://github.com/rust-lang/crates.io-index" 643 | checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" 644 | dependencies = [ 645 | "unicode-width", 646 | ] 647 | 648 | [[package]] 649 | name = "getrandom" 650 | version = "0.1.16" 651 | source = "registry+https://github.com/rust-lang/crates.io-index" 652 | checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" 653 | dependencies = [ 654 | "cfg-if", 655 | "libc", 656 | "wasi 0.9.0+wasi-snapshot-preview1", 657 | ] 658 | 659 | [[package]] 660 | name = "getrandom" 661 | version = "0.2.15" 662 | source = "registry+https://github.com/rust-lang/crates.io-index" 663 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 664 | dependencies = [ 665 | "cfg-if", 666 | "libc", 667 | "wasi 0.11.0+wasi-snapshot-preview1", 668 | ] 669 | 670 | [[package]] 671 | name = "gimli" 672 | version = "0.31.1" 673 | source = "registry+https://github.com/rust-lang/crates.io-index" 674 | checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" 675 | 676 | [[package]] 677 | name = "h2" 678 | version = "0.3.26" 679 | source = "registry+https://github.com/rust-lang/crates.io-index" 680 | checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" 681 | dependencies = [ 682 | "bytes", 683 | "fnv", 684 | "futures-core", 685 | "futures-sink", 686 | "futures-util", 687 | "http", 688 | "indexmap", 689 | "slab", 690 | "tokio", 691 | "tokio-util", 692 | "tracing", 693 | ] 694 | 695 | [[package]] 696 | name = "hashbrown" 697 | version = "0.15.0" 698 | source = "registry+https://github.com/rust-lang/crates.io-index" 699 | checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" 700 | 701 | [[package]] 702 | name = "hermit-abi" 703 | version = "0.3.9" 704 | source = "registry+https://github.com/rust-lang/crates.io-index" 705 | checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" 706 | 707 | [[package]] 708 | name = "html5ever" 709 | version = "0.25.2" 710 | source = "registry+https://github.com/rust-lang/crates.io-index" 711 | checksum = "e5c13fb08e5d4dfc151ee5e88bae63f7773d61852f3bdc73c9f4b9e1bde03148" 712 | dependencies = [ 713 | "log", 714 | "mac", 715 | "markup5ever", 716 | "proc-macro2", 717 | "quote", 718 | "syn 1.0.109", 719 | ] 720 | 721 | [[package]] 722 | name = "http" 723 | version = "0.2.12" 724 | source = "registry+https://github.com/rust-lang/crates.io-index" 725 | checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" 726 | dependencies = [ 727 | "bytes", 728 | "fnv", 729 | "itoa 1.0.11", 730 | ] 731 | 732 | [[package]] 733 | name = "http-body" 734 | version = "0.4.6" 735 | source = "registry+https://github.com/rust-lang/crates.io-index" 736 | checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" 737 | dependencies = [ 738 | "bytes", 739 | "http", 740 | "pin-project-lite", 741 | ] 742 | 743 | [[package]] 744 | name = "httparse" 745 | version = "1.9.5" 746 | source = "registry+https://github.com/rust-lang/crates.io-index" 747 | checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" 748 | 749 | [[package]] 750 | name = "httpdate" 751 | version = "1.0.3" 752 | source = "registry+https://github.com/rust-lang/crates.io-index" 753 | checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" 754 | 755 | [[package]] 756 | name = "hyper" 757 | version = "0.14.31" 758 | source = "registry+https://github.com/rust-lang/crates.io-index" 759 | checksum = "8c08302e8fa335b151b788c775ff56e7a03ae64ff85c548ee820fecb70356e85" 760 | dependencies = [ 761 | "bytes", 762 | "futures-channel", 763 | "futures-core", 764 | "futures-util", 765 | "h2", 766 | "http", 767 | "http-body", 768 | "httparse", 769 | "httpdate", 770 | "itoa 1.0.11", 771 | "pin-project-lite", 772 | "socket2", 773 | "tokio", 774 | "tower-service", 775 | "tracing", 776 | "want", 777 | ] 778 | 779 | [[package]] 780 | name = "hyper-tls" 781 | version = "0.5.0" 782 | source = "registry+https://github.com/rust-lang/crates.io-index" 783 | checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" 784 | dependencies = [ 785 | "bytes", 786 | "hyper", 787 | "native-tls", 788 | "tokio", 789 | "tokio-native-tls", 790 | ] 791 | 792 | [[package]] 793 | name = "idna" 794 | version = "0.5.0" 795 | source = "registry+https://github.com/rust-lang/crates.io-index" 796 | checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" 797 | dependencies = [ 798 | "unicode-bidi", 799 | "unicode-normalization", 800 | ] 801 | 802 | [[package]] 803 | name = "impl-more" 804 | version = "0.1.8" 805 | source = "registry+https://github.com/rust-lang/crates.io-index" 806 | checksum = "aae21c3177a27788957044151cc2800043d127acaa460a47ebb9b84dfa2c6aa0" 807 | 808 | [[package]] 809 | name = "indexmap" 810 | version = "2.6.0" 811 | source = "registry+https://github.com/rust-lang/crates.io-index" 812 | checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" 813 | dependencies = [ 814 | "equivalent", 815 | "hashbrown", 816 | ] 817 | 818 | [[package]] 819 | name = "ipnet" 820 | version = "2.10.1" 821 | source = "registry+https://github.com/rust-lang/crates.io-index" 822 | checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" 823 | 824 | [[package]] 825 | name = "itoa" 826 | version = "0.4.8" 827 | source = "registry+https://github.com/rust-lang/crates.io-index" 828 | checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" 829 | 830 | [[package]] 831 | name = "itoa" 832 | version = "1.0.11" 833 | source = "registry+https://github.com/rust-lang/crates.io-index" 834 | checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" 835 | 836 | [[package]] 837 | name = "jobserver" 838 | version = "0.1.32" 839 | source = "registry+https://github.com/rust-lang/crates.io-index" 840 | checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" 841 | dependencies = [ 842 | "libc", 843 | ] 844 | 845 | [[package]] 846 | name = "js-sys" 847 | version = "0.3.72" 848 | source = "registry+https://github.com/rust-lang/crates.io-index" 849 | checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" 850 | dependencies = [ 851 | "wasm-bindgen", 852 | ] 853 | 854 | [[package]] 855 | name = "language-tags" 856 | version = "0.3.2" 857 | source = "registry+https://github.com/rust-lang/crates.io-index" 858 | checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" 859 | 860 | [[package]] 861 | name = "libc" 862 | version = "0.2.161" 863 | source = "registry+https://github.com/rust-lang/crates.io-index" 864 | checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" 865 | 866 | [[package]] 867 | name = "linux-raw-sys" 868 | version = "0.4.14" 869 | source = "registry+https://github.com/rust-lang/crates.io-index" 870 | checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" 871 | 872 | [[package]] 873 | name = "local-channel" 874 | version = "0.1.5" 875 | source = "registry+https://github.com/rust-lang/crates.io-index" 876 | checksum = "b6cbc85e69b8df4b8bb8b89ec634e7189099cea8927a276b7384ce5488e53ec8" 877 | dependencies = [ 878 | "futures-core", 879 | "futures-sink", 880 | "local-waker", 881 | ] 882 | 883 | [[package]] 884 | name = "local-waker" 885 | version = "0.1.4" 886 | source = "registry+https://github.com/rust-lang/crates.io-index" 887 | checksum = "4d873d7c67ce09b42110d801813efbc9364414e356be9935700d368351657487" 888 | 889 | [[package]] 890 | name = "lock_api" 891 | version = "0.4.12" 892 | source = "registry+https://github.com/rust-lang/crates.io-index" 893 | checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" 894 | dependencies = [ 895 | "autocfg", 896 | "scopeguard", 897 | ] 898 | 899 | [[package]] 900 | name = "log" 901 | version = "0.4.22" 902 | source = "registry+https://github.com/rust-lang/crates.io-index" 903 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" 904 | 905 | [[package]] 906 | name = "mac" 907 | version = "0.1.1" 908 | source = "registry+https://github.com/rust-lang/crates.io-index" 909 | checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" 910 | 911 | [[package]] 912 | name = "markup5ever" 913 | version = "0.10.1" 914 | source = "registry+https://github.com/rust-lang/crates.io-index" 915 | checksum = "a24f40fb03852d1cdd84330cddcaf98e9ec08a7b7768e952fad3b4cf048ec8fd" 916 | dependencies = [ 917 | "log", 918 | "phf", 919 | "phf_codegen", 920 | "string_cache", 921 | "string_cache_codegen", 922 | "tendril", 923 | ] 924 | 925 | [[package]] 926 | name = "matches" 927 | version = "0.1.10" 928 | source = "registry+https://github.com/rust-lang/crates.io-index" 929 | checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" 930 | 931 | [[package]] 932 | name = "memchr" 933 | version = "2.7.4" 934 | source = "registry+https://github.com/rust-lang/crates.io-index" 935 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 936 | 937 | [[package]] 938 | name = "mime" 939 | version = "0.3.17" 940 | source = "registry+https://github.com/rust-lang/crates.io-index" 941 | checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 942 | 943 | [[package]] 944 | name = "miniz_oxide" 945 | version = "0.8.0" 946 | source = "registry+https://github.com/rust-lang/crates.io-index" 947 | checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" 948 | dependencies = [ 949 | "adler2", 950 | ] 951 | 952 | [[package]] 953 | name = "mio" 954 | version = "1.0.2" 955 | source = "registry+https://github.com/rust-lang/crates.io-index" 956 | checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" 957 | dependencies = [ 958 | "hermit-abi", 959 | "libc", 960 | "log", 961 | "wasi 0.11.0+wasi-snapshot-preview1", 962 | "windows-sys 0.52.0", 963 | ] 964 | 965 | [[package]] 966 | name = "native-tls" 967 | version = "0.2.12" 968 | source = "registry+https://github.com/rust-lang/crates.io-index" 969 | checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" 970 | dependencies = [ 971 | "libc", 972 | "log", 973 | "openssl", 974 | "openssl-probe", 975 | "openssl-sys", 976 | "schannel", 977 | "security-framework", 978 | "security-framework-sys", 979 | "tempfile", 980 | ] 981 | 982 | [[package]] 983 | name = "new_debug_unreachable" 984 | version = "1.0.6" 985 | source = "registry+https://github.com/rust-lang/crates.io-index" 986 | checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" 987 | 988 | [[package]] 989 | name = "nodrop" 990 | version = "0.1.14" 991 | source = "registry+https://github.com/rust-lang/crates.io-index" 992 | checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" 993 | 994 | [[package]] 995 | name = "num-conv" 996 | version = "0.1.0" 997 | source = "registry+https://github.com/rust-lang/crates.io-index" 998 | checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" 999 | 1000 | [[package]] 1001 | name = "object" 1002 | version = "0.36.5" 1003 | source = "registry+https://github.com/rust-lang/crates.io-index" 1004 | checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" 1005 | dependencies = [ 1006 | "memchr", 1007 | ] 1008 | 1009 | [[package]] 1010 | name = "once_cell" 1011 | version = "1.20.2" 1012 | source = "registry+https://github.com/rust-lang/crates.io-index" 1013 | checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" 1014 | 1015 | [[package]] 1016 | name = "openssl" 1017 | version = "0.10.68" 1018 | source = "registry+https://github.com/rust-lang/crates.io-index" 1019 | checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" 1020 | dependencies = [ 1021 | "bitflags 2.6.0", 1022 | "cfg-if", 1023 | "foreign-types", 1024 | "libc", 1025 | "once_cell", 1026 | "openssl-macros", 1027 | "openssl-sys", 1028 | ] 1029 | 1030 | [[package]] 1031 | name = "openssl-macros" 1032 | version = "0.1.1" 1033 | source = "registry+https://github.com/rust-lang/crates.io-index" 1034 | checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" 1035 | dependencies = [ 1036 | "proc-macro2", 1037 | "quote", 1038 | "syn 2.0.85", 1039 | ] 1040 | 1041 | [[package]] 1042 | name = "openssl-probe" 1043 | version = "0.1.5" 1044 | source = "registry+https://github.com/rust-lang/crates.io-index" 1045 | checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" 1046 | 1047 | [[package]] 1048 | name = "openssl-sys" 1049 | version = "0.9.104" 1050 | source = "registry+https://github.com/rust-lang/crates.io-index" 1051 | checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" 1052 | dependencies = [ 1053 | "cc", 1054 | "libc", 1055 | "pkg-config", 1056 | "vcpkg", 1057 | ] 1058 | 1059 | [[package]] 1060 | name = "parking_lot" 1061 | version = "0.12.3" 1062 | source = "registry+https://github.com/rust-lang/crates.io-index" 1063 | checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" 1064 | dependencies = [ 1065 | "lock_api", 1066 | "parking_lot_core", 1067 | ] 1068 | 1069 | [[package]] 1070 | name = "parking_lot_core" 1071 | version = "0.9.10" 1072 | source = "registry+https://github.com/rust-lang/crates.io-index" 1073 | checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" 1074 | dependencies = [ 1075 | "cfg-if", 1076 | "libc", 1077 | "redox_syscall", 1078 | "smallvec", 1079 | "windows-targets 0.52.6", 1080 | ] 1081 | 1082 | [[package]] 1083 | name = "paste" 1084 | version = "1.0.15" 1085 | source = "registry+https://github.com/rust-lang/crates.io-index" 1086 | checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" 1087 | 1088 | [[package]] 1089 | name = "percent-encoding" 1090 | version = "2.3.1" 1091 | source = "registry+https://github.com/rust-lang/crates.io-index" 1092 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 1093 | 1094 | [[package]] 1095 | name = "phf" 1096 | version = "0.8.0" 1097 | source = "registry+https://github.com/rust-lang/crates.io-index" 1098 | checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" 1099 | dependencies = [ 1100 | "phf_macros", 1101 | "phf_shared 0.8.0", 1102 | "proc-macro-hack", 1103 | ] 1104 | 1105 | [[package]] 1106 | name = "phf_codegen" 1107 | version = "0.8.0" 1108 | source = "registry+https://github.com/rust-lang/crates.io-index" 1109 | checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" 1110 | dependencies = [ 1111 | "phf_generator 0.8.0", 1112 | "phf_shared 0.8.0", 1113 | ] 1114 | 1115 | [[package]] 1116 | name = "phf_generator" 1117 | version = "0.8.0" 1118 | source = "registry+https://github.com/rust-lang/crates.io-index" 1119 | checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" 1120 | dependencies = [ 1121 | "phf_shared 0.8.0", 1122 | "rand 0.7.3", 1123 | ] 1124 | 1125 | [[package]] 1126 | name = "phf_generator" 1127 | version = "0.10.0" 1128 | source = "registry+https://github.com/rust-lang/crates.io-index" 1129 | checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" 1130 | dependencies = [ 1131 | "phf_shared 0.10.0", 1132 | "rand 0.8.5", 1133 | ] 1134 | 1135 | [[package]] 1136 | name = "phf_macros" 1137 | version = "0.8.0" 1138 | source = "registry+https://github.com/rust-lang/crates.io-index" 1139 | checksum = "7f6fde18ff429ffc8fe78e2bf7f8b7a5a5a6e2a8b58bc5a9ac69198bbda9189c" 1140 | dependencies = [ 1141 | "phf_generator 0.8.0", 1142 | "phf_shared 0.8.0", 1143 | "proc-macro-hack", 1144 | "proc-macro2", 1145 | "quote", 1146 | "syn 1.0.109", 1147 | ] 1148 | 1149 | [[package]] 1150 | name = "phf_shared" 1151 | version = "0.8.0" 1152 | source = "registry+https://github.com/rust-lang/crates.io-index" 1153 | checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" 1154 | dependencies = [ 1155 | "siphasher", 1156 | ] 1157 | 1158 | [[package]] 1159 | name = "phf_shared" 1160 | version = "0.10.0" 1161 | source = "registry+https://github.com/rust-lang/crates.io-index" 1162 | checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" 1163 | dependencies = [ 1164 | "siphasher", 1165 | ] 1166 | 1167 | [[package]] 1168 | name = "pin-project-lite" 1169 | version = "0.2.15" 1170 | source = "registry+https://github.com/rust-lang/crates.io-index" 1171 | checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" 1172 | 1173 | [[package]] 1174 | name = "pin-utils" 1175 | version = "0.1.0" 1176 | source = "registry+https://github.com/rust-lang/crates.io-index" 1177 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 1178 | 1179 | [[package]] 1180 | name = "pkg-config" 1181 | version = "0.3.31" 1182 | source = "registry+https://github.com/rust-lang/crates.io-index" 1183 | checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" 1184 | 1185 | [[package]] 1186 | name = "powerfmt" 1187 | version = "0.2.0" 1188 | source = "registry+https://github.com/rust-lang/crates.io-index" 1189 | checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" 1190 | 1191 | [[package]] 1192 | name = "ppv-lite86" 1193 | version = "0.2.20" 1194 | source = "registry+https://github.com/rust-lang/crates.io-index" 1195 | checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" 1196 | dependencies = [ 1197 | "zerocopy", 1198 | ] 1199 | 1200 | [[package]] 1201 | name = "precomputed-hash" 1202 | version = "0.1.1" 1203 | source = "registry+https://github.com/rust-lang/crates.io-index" 1204 | checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" 1205 | 1206 | [[package]] 1207 | name = "proc-macro-hack" 1208 | version = "0.5.20+deprecated" 1209 | source = "registry+https://github.com/rust-lang/crates.io-index" 1210 | checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" 1211 | 1212 | [[package]] 1213 | name = "proc-macro2" 1214 | version = "1.0.89" 1215 | source = "registry+https://github.com/rust-lang/crates.io-index" 1216 | checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" 1217 | dependencies = [ 1218 | "unicode-ident", 1219 | ] 1220 | 1221 | [[package]] 1222 | name = "quote" 1223 | version = "1.0.37" 1224 | source = "registry+https://github.com/rust-lang/crates.io-index" 1225 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 1226 | dependencies = [ 1227 | "proc-macro2", 1228 | ] 1229 | 1230 | [[package]] 1231 | name = "rand" 1232 | version = "0.7.3" 1233 | source = "registry+https://github.com/rust-lang/crates.io-index" 1234 | checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" 1235 | dependencies = [ 1236 | "getrandom 0.1.16", 1237 | "libc", 1238 | "rand_chacha 0.2.2", 1239 | "rand_core 0.5.1", 1240 | "rand_hc", 1241 | "rand_pcg", 1242 | ] 1243 | 1244 | [[package]] 1245 | name = "rand" 1246 | version = "0.8.5" 1247 | source = "registry+https://github.com/rust-lang/crates.io-index" 1248 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 1249 | dependencies = [ 1250 | "libc", 1251 | "rand_chacha 0.3.1", 1252 | "rand_core 0.6.4", 1253 | ] 1254 | 1255 | [[package]] 1256 | name = "rand_chacha" 1257 | version = "0.2.2" 1258 | source = "registry+https://github.com/rust-lang/crates.io-index" 1259 | checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" 1260 | dependencies = [ 1261 | "ppv-lite86", 1262 | "rand_core 0.5.1", 1263 | ] 1264 | 1265 | [[package]] 1266 | name = "rand_chacha" 1267 | version = "0.3.1" 1268 | source = "registry+https://github.com/rust-lang/crates.io-index" 1269 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 1270 | dependencies = [ 1271 | "ppv-lite86", 1272 | "rand_core 0.6.4", 1273 | ] 1274 | 1275 | [[package]] 1276 | name = "rand_core" 1277 | version = "0.5.1" 1278 | source = "registry+https://github.com/rust-lang/crates.io-index" 1279 | checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" 1280 | dependencies = [ 1281 | "getrandom 0.1.16", 1282 | ] 1283 | 1284 | [[package]] 1285 | name = "rand_core" 1286 | version = "0.6.4" 1287 | source = "registry+https://github.com/rust-lang/crates.io-index" 1288 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 1289 | dependencies = [ 1290 | "getrandom 0.2.15", 1291 | ] 1292 | 1293 | [[package]] 1294 | name = "rand_hc" 1295 | version = "0.2.0" 1296 | source = "registry+https://github.com/rust-lang/crates.io-index" 1297 | checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" 1298 | dependencies = [ 1299 | "rand_core 0.5.1", 1300 | ] 1301 | 1302 | [[package]] 1303 | name = "rand_pcg" 1304 | version = "0.2.1" 1305 | source = "registry+https://github.com/rust-lang/crates.io-index" 1306 | checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" 1307 | dependencies = [ 1308 | "rand_core 0.5.1", 1309 | ] 1310 | 1311 | [[package]] 1312 | name = "redox_syscall" 1313 | version = "0.5.7" 1314 | source = "registry+https://github.com/rust-lang/crates.io-index" 1315 | checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" 1316 | dependencies = [ 1317 | "bitflags 2.6.0", 1318 | ] 1319 | 1320 | [[package]] 1321 | name = "regex" 1322 | version = "1.11.1" 1323 | source = "registry+https://github.com/rust-lang/crates.io-index" 1324 | checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 1325 | dependencies = [ 1326 | "aho-corasick", 1327 | "memchr", 1328 | "regex-automata", 1329 | "regex-syntax", 1330 | ] 1331 | 1332 | [[package]] 1333 | name = "regex-automata" 1334 | version = "0.4.8" 1335 | source = "registry+https://github.com/rust-lang/crates.io-index" 1336 | checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" 1337 | dependencies = [ 1338 | "aho-corasick", 1339 | "memchr", 1340 | "regex-syntax", 1341 | ] 1342 | 1343 | [[package]] 1344 | name = "regex-lite" 1345 | version = "0.1.6" 1346 | source = "registry+https://github.com/rust-lang/crates.io-index" 1347 | checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a" 1348 | 1349 | [[package]] 1350 | name = "regex-syntax" 1351 | version = "0.8.5" 1352 | source = "registry+https://github.com/rust-lang/crates.io-index" 1353 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 1354 | 1355 | [[package]] 1356 | name = "reqwest" 1357 | version = "0.11.27" 1358 | source = "registry+https://github.com/rust-lang/crates.io-index" 1359 | checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" 1360 | dependencies = [ 1361 | "base64 0.21.7", 1362 | "bytes", 1363 | "encoding_rs", 1364 | "futures-core", 1365 | "futures-util", 1366 | "h2", 1367 | "http", 1368 | "http-body", 1369 | "hyper", 1370 | "hyper-tls", 1371 | "ipnet", 1372 | "js-sys", 1373 | "log", 1374 | "mime", 1375 | "native-tls", 1376 | "once_cell", 1377 | "percent-encoding", 1378 | "pin-project-lite", 1379 | "rustls-pemfile", 1380 | "serde", 1381 | "serde_json", 1382 | "serde_urlencoded", 1383 | "sync_wrapper", 1384 | "system-configuration", 1385 | "tokio", 1386 | "tokio-native-tls", 1387 | "tower-service", 1388 | "url", 1389 | "wasm-bindgen", 1390 | "wasm-bindgen-futures", 1391 | "web-sys", 1392 | "winreg", 1393 | ] 1394 | 1395 | [[package]] 1396 | name = "rust-web-scraper" 1397 | version = "0.1.0" 1398 | dependencies = [ 1399 | "actix-web", 1400 | "reqwest", 1401 | "scraper", 1402 | "serde", 1403 | "serde_json", 1404 | "tokio", 1405 | ] 1406 | 1407 | [[package]] 1408 | name = "rustc-demangle" 1409 | version = "0.1.24" 1410 | source = "registry+https://github.com/rust-lang/crates.io-index" 1411 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 1412 | 1413 | [[package]] 1414 | name = "rustc_version" 1415 | version = "0.4.1" 1416 | source = "registry+https://github.com/rust-lang/crates.io-index" 1417 | checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" 1418 | dependencies = [ 1419 | "semver", 1420 | ] 1421 | 1422 | [[package]] 1423 | name = "rustix" 1424 | version = "0.38.38" 1425 | source = "registry+https://github.com/rust-lang/crates.io-index" 1426 | checksum = "aa260229e6538e52293eeb577aabd09945a09d6d9cc0fc550ed7529056c2e32a" 1427 | dependencies = [ 1428 | "bitflags 2.6.0", 1429 | "errno", 1430 | "libc", 1431 | "linux-raw-sys", 1432 | "windows-sys 0.52.0", 1433 | ] 1434 | 1435 | [[package]] 1436 | name = "rustls-pemfile" 1437 | version = "1.0.4" 1438 | source = "registry+https://github.com/rust-lang/crates.io-index" 1439 | checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" 1440 | dependencies = [ 1441 | "base64 0.21.7", 1442 | ] 1443 | 1444 | [[package]] 1445 | name = "ryu" 1446 | version = "1.0.18" 1447 | source = "registry+https://github.com/rust-lang/crates.io-index" 1448 | checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" 1449 | 1450 | [[package]] 1451 | name = "schannel" 1452 | version = "0.1.26" 1453 | source = "registry+https://github.com/rust-lang/crates.io-index" 1454 | checksum = "01227be5826fa0690321a2ba6c5cd57a19cf3f6a09e76973b58e61de6ab9d1c1" 1455 | dependencies = [ 1456 | "windows-sys 0.59.0", 1457 | ] 1458 | 1459 | [[package]] 1460 | name = "scopeguard" 1461 | version = "1.2.0" 1462 | source = "registry+https://github.com/rust-lang/crates.io-index" 1463 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 1464 | 1465 | [[package]] 1466 | name = "scraper" 1467 | version = "0.12.0" 1468 | source = "registry+https://github.com/rust-lang/crates.io-index" 1469 | checksum = "48e02aa790c80c2e494130dec6a522033b6a23603ffc06360e9fe6c611ea2c12" 1470 | dependencies = [ 1471 | "cssparser", 1472 | "ego-tree", 1473 | "getopts", 1474 | "html5ever", 1475 | "matches", 1476 | "selectors", 1477 | "smallvec", 1478 | "tendril", 1479 | ] 1480 | 1481 | [[package]] 1482 | name = "security-framework" 1483 | version = "2.11.1" 1484 | source = "registry+https://github.com/rust-lang/crates.io-index" 1485 | checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" 1486 | dependencies = [ 1487 | "bitflags 2.6.0", 1488 | "core-foundation", 1489 | "core-foundation-sys", 1490 | "libc", 1491 | "security-framework-sys", 1492 | ] 1493 | 1494 | [[package]] 1495 | name = "security-framework-sys" 1496 | version = "2.12.0" 1497 | source = "registry+https://github.com/rust-lang/crates.io-index" 1498 | checksum = "ea4a292869320c0272d7bc55a5a6aafaff59b4f63404a003887b679a2e05b4b6" 1499 | dependencies = [ 1500 | "core-foundation-sys", 1501 | "libc", 1502 | ] 1503 | 1504 | [[package]] 1505 | name = "selectors" 1506 | version = "0.22.0" 1507 | source = "registry+https://github.com/rust-lang/crates.io-index" 1508 | checksum = "df320f1889ac4ba6bc0cdc9c9af7af4bd64bb927bccdf32d81140dc1f9be12fe" 1509 | dependencies = [ 1510 | "bitflags 1.3.2", 1511 | "cssparser", 1512 | "derive_more", 1513 | "fxhash", 1514 | "log", 1515 | "matches", 1516 | "phf", 1517 | "phf_codegen", 1518 | "precomputed-hash", 1519 | "servo_arc", 1520 | "smallvec", 1521 | "thin-slice", 1522 | ] 1523 | 1524 | [[package]] 1525 | name = "semver" 1526 | version = "1.0.23" 1527 | source = "registry+https://github.com/rust-lang/crates.io-index" 1528 | checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" 1529 | 1530 | [[package]] 1531 | name = "serde" 1532 | version = "1.0.213" 1533 | source = "registry+https://github.com/rust-lang/crates.io-index" 1534 | checksum = "3ea7893ff5e2466df8d720bb615088341b295f849602c6956047f8f80f0e9bc1" 1535 | dependencies = [ 1536 | "serde_derive", 1537 | ] 1538 | 1539 | [[package]] 1540 | name = "serde_derive" 1541 | version = "1.0.213" 1542 | source = "registry+https://github.com/rust-lang/crates.io-index" 1543 | checksum = "7e85ad2009c50b58e87caa8cd6dac16bdf511bbfb7af6c33df902396aa480fa5" 1544 | dependencies = [ 1545 | "proc-macro2", 1546 | "quote", 1547 | "syn 2.0.85", 1548 | ] 1549 | 1550 | [[package]] 1551 | name = "serde_json" 1552 | version = "1.0.132" 1553 | source = "registry+https://github.com/rust-lang/crates.io-index" 1554 | checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" 1555 | dependencies = [ 1556 | "itoa 1.0.11", 1557 | "memchr", 1558 | "ryu", 1559 | "serde", 1560 | ] 1561 | 1562 | [[package]] 1563 | name = "serde_urlencoded" 1564 | version = "0.7.1" 1565 | source = "registry+https://github.com/rust-lang/crates.io-index" 1566 | checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 1567 | dependencies = [ 1568 | "form_urlencoded", 1569 | "itoa 1.0.11", 1570 | "ryu", 1571 | "serde", 1572 | ] 1573 | 1574 | [[package]] 1575 | name = "servo_arc" 1576 | version = "0.1.1" 1577 | source = "registry+https://github.com/rust-lang/crates.io-index" 1578 | checksum = "d98238b800e0d1576d8b6e3de32827c2d74bee68bb97748dcf5071fb53965432" 1579 | dependencies = [ 1580 | "nodrop", 1581 | "stable_deref_trait", 1582 | ] 1583 | 1584 | [[package]] 1585 | name = "sha1" 1586 | version = "0.10.6" 1587 | source = "registry+https://github.com/rust-lang/crates.io-index" 1588 | checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" 1589 | dependencies = [ 1590 | "cfg-if", 1591 | "cpufeatures", 1592 | "digest", 1593 | ] 1594 | 1595 | [[package]] 1596 | name = "shlex" 1597 | version = "1.3.0" 1598 | source = "registry+https://github.com/rust-lang/crates.io-index" 1599 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 1600 | 1601 | [[package]] 1602 | name = "signal-hook-registry" 1603 | version = "1.4.2" 1604 | source = "registry+https://github.com/rust-lang/crates.io-index" 1605 | checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" 1606 | dependencies = [ 1607 | "libc", 1608 | ] 1609 | 1610 | [[package]] 1611 | name = "siphasher" 1612 | version = "0.3.11" 1613 | source = "registry+https://github.com/rust-lang/crates.io-index" 1614 | checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" 1615 | 1616 | [[package]] 1617 | name = "slab" 1618 | version = "0.4.9" 1619 | source = "registry+https://github.com/rust-lang/crates.io-index" 1620 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 1621 | dependencies = [ 1622 | "autocfg", 1623 | ] 1624 | 1625 | [[package]] 1626 | name = "smallvec" 1627 | version = "1.13.2" 1628 | source = "registry+https://github.com/rust-lang/crates.io-index" 1629 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 1630 | 1631 | [[package]] 1632 | name = "socket2" 1633 | version = "0.5.7" 1634 | source = "registry+https://github.com/rust-lang/crates.io-index" 1635 | checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" 1636 | dependencies = [ 1637 | "libc", 1638 | "windows-sys 0.52.0", 1639 | ] 1640 | 1641 | [[package]] 1642 | name = "stable_deref_trait" 1643 | version = "1.2.0" 1644 | source = "registry+https://github.com/rust-lang/crates.io-index" 1645 | checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" 1646 | 1647 | [[package]] 1648 | name = "string_cache" 1649 | version = "0.8.7" 1650 | source = "registry+https://github.com/rust-lang/crates.io-index" 1651 | checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b" 1652 | dependencies = [ 1653 | "new_debug_unreachable", 1654 | "once_cell", 1655 | "parking_lot", 1656 | "phf_shared 0.10.0", 1657 | "precomputed-hash", 1658 | "serde", 1659 | ] 1660 | 1661 | [[package]] 1662 | name = "string_cache_codegen" 1663 | version = "0.5.2" 1664 | source = "registry+https://github.com/rust-lang/crates.io-index" 1665 | checksum = "6bb30289b722be4ff74a408c3cc27edeaad656e06cb1fe8fa9231fa59c728988" 1666 | dependencies = [ 1667 | "phf_generator 0.10.0", 1668 | "phf_shared 0.10.0", 1669 | "proc-macro2", 1670 | "quote", 1671 | ] 1672 | 1673 | [[package]] 1674 | name = "syn" 1675 | version = "1.0.109" 1676 | source = "registry+https://github.com/rust-lang/crates.io-index" 1677 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 1678 | dependencies = [ 1679 | "proc-macro2", 1680 | "quote", 1681 | "unicode-ident", 1682 | ] 1683 | 1684 | [[package]] 1685 | name = "syn" 1686 | version = "2.0.85" 1687 | source = "registry+https://github.com/rust-lang/crates.io-index" 1688 | checksum = "5023162dfcd14ef8f32034d8bcd4cc5ddc61ef7a247c024a33e24e1f24d21b56" 1689 | dependencies = [ 1690 | "proc-macro2", 1691 | "quote", 1692 | "unicode-ident", 1693 | ] 1694 | 1695 | [[package]] 1696 | name = "sync_wrapper" 1697 | version = "0.1.2" 1698 | source = "registry+https://github.com/rust-lang/crates.io-index" 1699 | checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" 1700 | 1701 | [[package]] 1702 | name = "system-configuration" 1703 | version = "0.5.1" 1704 | source = "registry+https://github.com/rust-lang/crates.io-index" 1705 | checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" 1706 | dependencies = [ 1707 | "bitflags 1.3.2", 1708 | "core-foundation", 1709 | "system-configuration-sys", 1710 | ] 1711 | 1712 | [[package]] 1713 | name = "system-configuration-sys" 1714 | version = "0.5.0" 1715 | source = "registry+https://github.com/rust-lang/crates.io-index" 1716 | checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" 1717 | dependencies = [ 1718 | "core-foundation-sys", 1719 | "libc", 1720 | ] 1721 | 1722 | [[package]] 1723 | name = "tempfile" 1724 | version = "3.13.0" 1725 | source = "registry+https://github.com/rust-lang/crates.io-index" 1726 | checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" 1727 | dependencies = [ 1728 | "cfg-if", 1729 | "fastrand", 1730 | "once_cell", 1731 | "rustix", 1732 | "windows-sys 0.59.0", 1733 | ] 1734 | 1735 | [[package]] 1736 | name = "tendril" 1737 | version = "0.4.3" 1738 | source = "registry+https://github.com/rust-lang/crates.io-index" 1739 | checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" 1740 | dependencies = [ 1741 | "futf", 1742 | "mac", 1743 | "utf-8", 1744 | ] 1745 | 1746 | [[package]] 1747 | name = "thin-slice" 1748 | version = "0.1.1" 1749 | source = "registry+https://github.com/rust-lang/crates.io-index" 1750 | checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c" 1751 | 1752 | [[package]] 1753 | name = "time" 1754 | version = "0.3.36" 1755 | source = "registry+https://github.com/rust-lang/crates.io-index" 1756 | checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" 1757 | dependencies = [ 1758 | "deranged", 1759 | "itoa 1.0.11", 1760 | "num-conv", 1761 | "powerfmt", 1762 | "serde", 1763 | "time-core", 1764 | "time-macros", 1765 | ] 1766 | 1767 | [[package]] 1768 | name = "time-core" 1769 | version = "0.1.2" 1770 | source = "registry+https://github.com/rust-lang/crates.io-index" 1771 | checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" 1772 | 1773 | [[package]] 1774 | name = "time-macros" 1775 | version = "0.2.18" 1776 | source = "registry+https://github.com/rust-lang/crates.io-index" 1777 | checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" 1778 | dependencies = [ 1779 | "num-conv", 1780 | "time-core", 1781 | ] 1782 | 1783 | [[package]] 1784 | name = "tinyvec" 1785 | version = "1.8.0" 1786 | source = "registry+https://github.com/rust-lang/crates.io-index" 1787 | checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" 1788 | dependencies = [ 1789 | "tinyvec_macros", 1790 | ] 1791 | 1792 | [[package]] 1793 | name = "tinyvec_macros" 1794 | version = "0.1.1" 1795 | source = "registry+https://github.com/rust-lang/crates.io-index" 1796 | checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 1797 | 1798 | [[package]] 1799 | name = "tokio" 1800 | version = "1.41.0" 1801 | source = "registry+https://github.com/rust-lang/crates.io-index" 1802 | checksum = "145f3413504347a2be84393cc8a7d2fb4d863b375909ea59f2158261aa258bbb" 1803 | dependencies = [ 1804 | "backtrace", 1805 | "bytes", 1806 | "libc", 1807 | "mio", 1808 | "parking_lot", 1809 | "pin-project-lite", 1810 | "signal-hook-registry", 1811 | "socket2", 1812 | "tokio-macros", 1813 | "windows-sys 0.52.0", 1814 | ] 1815 | 1816 | [[package]] 1817 | name = "tokio-macros" 1818 | version = "2.4.0" 1819 | source = "registry+https://github.com/rust-lang/crates.io-index" 1820 | checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" 1821 | dependencies = [ 1822 | "proc-macro2", 1823 | "quote", 1824 | "syn 2.0.85", 1825 | ] 1826 | 1827 | [[package]] 1828 | name = "tokio-native-tls" 1829 | version = "0.3.1" 1830 | source = "registry+https://github.com/rust-lang/crates.io-index" 1831 | checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" 1832 | dependencies = [ 1833 | "native-tls", 1834 | "tokio", 1835 | ] 1836 | 1837 | [[package]] 1838 | name = "tokio-util" 1839 | version = "0.7.12" 1840 | source = "registry+https://github.com/rust-lang/crates.io-index" 1841 | checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" 1842 | dependencies = [ 1843 | "bytes", 1844 | "futures-core", 1845 | "futures-sink", 1846 | "pin-project-lite", 1847 | "tokio", 1848 | ] 1849 | 1850 | [[package]] 1851 | name = "tower-service" 1852 | version = "0.3.3" 1853 | source = "registry+https://github.com/rust-lang/crates.io-index" 1854 | checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" 1855 | 1856 | [[package]] 1857 | name = "tracing" 1858 | version = "0.1.40" 1859 | source = "registry+https://github.com/rust-lang/crates.io-index" 1860 | checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" 1861 | dependencies = [ 1862 | "log", 1863 | "pin-project-lite", 1864 | "tracing-core", 1865 | ] 1866 | 1867 | [[package]] 1868 | name = "tracing-core" 1869 | version = "0.1.32" 1870 | source = "registry+https://github.com/rust-lang/crates.io-index" 1871 | checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" 1872 | dependencies = [ 1873 | "once_cell", 1874 | ] 1875 | 1876 | [[package]] 1877 | name = "try-lock" 1878 | version = "0.2.5" 1879 | source = "registry+https://github.com/rust-lang/crates.io-index" 1880 | checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" 1881 | 1882 | [[package]] 1883 | name = "typenum" 1884 | version = "1.17.0" 1885 | source = "registry+https://github.com/rust-lang/crates.io-index" 1886 | checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" 1887 | 1888 | [[package]] 1889 | name = "unicode-bidi" 1890 | version = "0.3.17" 1891 | source = "registry+https://github.com/rust-lang/crates.io-index" 1892 | checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" 1893 | 1894 | [[package]] 1895 | name = "unicode-ident" 1896 | version = "1.0.13" 1897 | source = "registry+https://github.com/rust-lang/crates.io-index" 1898 | checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" 1899 | 1900 | [[package]] 1901 | name = "unicode-normalization" 1902 | version = "0.1.24" 1903 | source = "registry+https://github.com/rust-lang/crates.io-index" 1904 | checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" 1905 | dependencies = [ 1906 | "tinyvec", 1907 | ] 1908 | 1909 | [[package]] 1910 | name = "unicode-width" 1911 | version = "0.1.14" 1912 | source = "registry+https://github.com/rust-lang/crates.io-index" 1913 | checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" 1914 | 1915 | [[package]] 1916 | name = "url" 1917 | version = "2.5.2" 1918 | source = "registry+https://github.com/rust-lang/crates.io-index" 1919 | checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" 1920 | dependencies = [ 1921 | "form_urlencoded", 1922 | "idna", 1923 | "percent-encoding", 1924 | ] 1925 | 1926 | [[package]] 1927 | name = "utf-8" 1928 | version = "0.7.6" 1929 | source = "registry+https://github.com/rust-lang/crates.io-index" 1930 | checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" 1931 | 1932 | [[package]] 1933 | name = "vcpkg" 1934 | version = "0.2.15" 1935 | source = "registry+https://github.com/rust-lang/crates.io-index" 1936 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 1937 | 1938 | [[package]] 1939 | name = "version_check" 1940 | version = "0.9.5" 1941 | source = "registry+https://github.com/rust-lang/crates.io-index" 1942 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 1943 | 1944 | [[package]] 1945 | name = "want" 1946 | version = "0.3.1" 1947 | source = "registry+https://github.com/rust-lang/crates.io-index" 1948 | checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" 1949 | dependencies = [ 1950 | "try-lock", 1951 | ] 1952 | 1953 | [[package]] 1954 | name = "wasi" 1955 | version = "0.9.0+wasi-snapshot-preview1" 1956 | source = "registry+https://github.com/rust-lang/crates.io-index" 1957 | checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" 1958 | 1959 | [[package]] 1960 | name = "wasi" 1961 | version = "0.11.0+wasi-snapshot-preview1" 1962 | source = "registry+https://github.com/rust-lang/crates.io-index" 1963 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1964 | 1965 | [[package]] 1966 | name = "wasm-bindgen" 1967 | version = "0.2.95" 1968 | source = "registry+https://github.com/rust-lang/crates.io-index" 1969 | checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" 1970 | dependencies = [ 1971 | "cfg-if", 1972 | "once_cell", 1973 | "wasm-bindgen-macro", 1974 | ] 1975 | 1976 | [[package]] 1977 | name = "wasm-bindgen-backend" 1978 | version = "0.2.95" 1979 | source = "registry+https://github.com/rust-lang/crates.io-index" 1980 | checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" 1981 | dependencies = [ 1982 | "bumpalo", 1983 | "log", 1984 | "once_cell", 1985 | "proc-macro2", 1986 | "quote", 1987 | "syn 2.0.85", 1988 | "wasm-bindgen-shared", 1989 | ] 1990 | 1991 | [[package]] 1992 | name = "wasm-bindgen-futures" 1993 | version = "0.4.45" 1994 | source = "registry+https://github.com/rust-lang/crates.io-index" 1995 | checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" 1996 | dependencies = [ 1997 | "cfg-if", 1998 | "js-sys", 1999 | "wasm-bindgen", 2000 | "web-sys", 2001 | ] 2002 | 2003 | [[package]] 2004 | name = "wasm-bindgen-macro" 2005 | version = "0.2.95" 2006 | source = "registry+https://github.com/rust-lang/crates.io-index" 2007 | checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" 2008 | dependencies = [ 2009 | "quote", 2010 | "wasm-bindgen-macro-support", 2011 | ] 2012 | 2013 | [[package]] 2014 | name = "wasm-bindgen-macro-support" 2015 | version = "0.2.95" 2016 | source = "registry+https://github.com/rust-lang/crates.io-index" 2017 | checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" 2018 | dependencies = [ 2019 | "proc-macro2", 2020 | "quote", 2021 | "syn 2.0.85", 2022 | "wasm-bindgen-backend", 2023 | "wasm-bindgen-shared", 2024 | ] 2025 | 2026 | [[package]] 2027 | name = "wasm-bindgen-shared" 2028 | version = "0.2.95" 2029 | source = "registry+https://github.com/rust-lang/crates.io-index" 2030 | checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" 2031 | 2032 | [[package]] 2033 | name = "web-sys" 2034 | version = "0.3.72" 2035 | source = "registry+https://github.com/rust-lang/crates.io-index" 2036 | checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" 2037 | dependencies = [ 2038 | "js-sys", 2039 | "wasm-bindgen", 2040 | ] 2041 | 2042 | [[package]] 2043 | name = "windows-sys" 2044 | version = "0.48.0" 2045 | source = "registry+https://github.com/rust-lang/crates.io-index" 2046 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 2047 | dependencies = [ 2048 | "windows-targets 0.48.5", 2049 | ] 2050 | 2051 | [[package]] 2052 | name = "windows-sys" 2053 | version = "0.52.0" 2054 | source = "registry+https://github.com/rust-lang/crates.io-index" 2055 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 2056 | dependencies = [ 2057 | "windows-targets 0.52.6", 2058 | ] 2059 | 2060 | [[package]] 2061 | name = "windows-sys" 2062 | version = "0.59.0" 2063 | source = "registry+https://github.com/rust-lang/crates.io-index" 2064 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 2065 | dependencies = [ 2066 | "windows-targets 0.52.6", 2067 | ] 2068 | 2069 | [[package]] 2070 | name = "windows-targets" 2071 | version = "0.48.5" 2072 | source = "registry+https://github.com/rust-lang/crates.io-index" 2073 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 2074 | dependencies = [ 2075 | "windows_aarch64_gnullvm 0.48.5", 2076 | "windows_aarch64_msvc 0.48.5", 2077 | "windows_i686_gnu 0.48.5", 2078 | "windows_i686_msvc 0.48.5", 2079 | "windows_x86_64_gnu 0.48.5", 2080 | "windows_x86_64_gnullvm 0.48.5", 2081 | "windows_x86_64_msvc 0.48.5", 2082 | ] 2083 | 2084 | [[package]] 2085 | name = "windows-targets" 2086 | version = "0.52.6" 2087 | source = "registry+https://github.com/rust-lang/crates.io-index" 2088 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 2089 | dependencies = [ 2090 | "windows_aarch64_gnullvm 0.52.6", 2091 | "windows_aarch64_msvc 0.52.6", 2092 | "windows_i686_gnu 0.52.6", 2093 | "windows_i686_gnullvm", 2094 | "windows_i686_msvc 0.52.6", 2095 | "windows_x86_64_gnu 0.52.6", 2096 | "windows_x86_64_gnullvm 0.52.6", 2097 | "windows_x86_64_msvc 0.52.6", 2098 | ] 2099 | 2100 | [[package]] 2101 | name = "windows_aarch64_gnullvm" 2102 | version = "0.48.5" 2103 | source = "registry+https://github.com/rust-lang/crates.io-index" 2104 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 2105 | 2106 | [[package]] 2107 | name = "windows_aarch64_gnullvm" 2108 | version = "0.52.6" 2109 | source = "registry+https://github.com/rust-lang/crates.io-index" 2110 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 2111 | 2112 | [[package]] 2113 | name = "windows_aarch64_msvc" 2114 | version = "0.48.5" 2115 | source = "registry+https://github.com/rust-lang/crates.io-index" 2116 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 2117 | 2118 | [[package]] 2119 | name = "windows_aarch64_msvc" 2120 | version = "0.52.6" 2121 | source = "registry+https://github.com/rust-lang/crates.io-index" 2122 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 2123 | 2124 | [[package]] 2125 | name = "windows_i686_gnu" 2126 | version = "0.48.5" 2127 | source = "registry+https://github.com/rust-lang/crates.io-index" 2128 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 2129 | 2130 | [[package]] 2131 | name = "windows_i686_gnu" 2132 | version = "0.52.6" 2133 | source = "registry+https://github.com/rust-lang/crates.io-index" 2134 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 2135 | 2136 | [[package]] 2137 | name = "windows_i686_gnullvm" 2138 | version = "0.52.6" 2139 | source = "registry+https://github.com/rust-lang/crates.io-index" 2140 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 2141 | 2142 | [[package]] 2143 | name = "windows_i686_msvc" 2144 | version = "0.48.5" 2145 | source = "registry+https://github.com/rust-lang/crates.io-index" 2146 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 2147 | 2148 | [[package]] 2149 | name = "windows_i686_msvc" 2150 | version = "0.52.6" 2151 | source = "registry+https://github.com/rust-lang/crates.io-index" 2152 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 2153 | 2154 | [[package]] 2155 | name = "windows_x86_64_gnu" 2156 | version = "0.48.5" 2157 | source = "registry+https://github.com/rust-lang/crates.io-index" 2158 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 2159 | 2160 | [[package]] 2161 | name = "windows_x86_64_gnu" 2162 | version = "0.52.6" 2163 | source = "registry+https://github.com/rust-lang/crates.io-index" 2164 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 2165 | 2166 | [[package]] 2167 | name = "windows_x86_64_gnullvm" 2168 | version = "0.48.5" 2169 | source = "registry+https://github.com/rust-lang/crates.io-index" 2170 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 2171 | 2172 | [[package]] 2173 | name = "windows_x86_64_gnullvm" 2174 | version = "0.52.6" 2175 | source = "registry+https://github.com/rust-lang/crates.io-index" 2176 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 2177 | 2178 | [[package]] 2179 | name = "windows_x86_64_msvc" 2180 | version = "0.48.5" 2181 | source = "registry+https://github.com/rust-lang/crates.io-index" 2182 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 2183 | 2184 | [[package]] 2185 | name = "windows_x86_64_msvc" 2186 | version = "0.52.6" 2187 | source = "registry+https://github.com/rust-lang/crates.io-index" 2188 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 2189 | 2190 | [[package]] 2191 | name = "winreg" 2192 | version = "0.50.0" 2193 | source = "registry+https://github.com/rust-lang/crates.io-index" 2194 | checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" 2195 | dependencies = [ 2196 | "cfg-if", 2197 | "windows-sys 0.48.0", 2198 | ] 2199 | 2200 | [[package]] 2201 | name = "zerocopy" 2202 | version = "0.7.35" 2203 | source = "registry+https://github.com/rust-lang/crates.io-index" 2204 | checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" 2205 | dependencies = [ 2206 | "byteorder", 2207 | "zerocopy-derive", 2208 | ] 2209 | 2210 | [[package]] 2211 | name = "zerocopy-derive" 2212 | version = "0.7.35" 2213 | source = "registry+https://github.com/rust-lang/crates.io-index" 2214 | checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" 2215 | dependencies = [ 2216 | "proc-macro2", 2217 | "quote", 2218 | "syn 2.0.85", 2219 | ] 2220 | 2221 | [[package]] 2222 | name = "zstd" 2223 | version = "0.13.2" 2224 | source = "registry+https://github.com/rust-lang/crates.io-index" 2225 | checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9" 2226 | dependencies = [ 2227 | "zstd-safe", 2228 | ] 2229 | 2230 | [[package]] 2231 | name = "zstd-safe" 2232 | version = "7.2.1" 2233 | source = "registry+https://github.com/rust-lang/crates.io-index" 2234 | checksum = "54a3ab4db68cea366acc5c897c7b4d4d1b8994a9cd6e6f841f8964566a419059" 2235 | dependencies = [ 2236 | "zstd-sys", 2237 | ] 2238 | 2239 | [[package]] 2240 | name = "zstd-sys" 2241 | version = "2.0.13+zstd.1.5.6" 2242 | source = "registry+https://github.com/rust-lang/crates.io-index" 2243 | checksum = "38ff0f21cfee8f97d94cef41359e0c89aa6113028ab0291aa8ca0038995a95aa" 2244 | dependencies = [ 2245 | "cc", 2246 | "pkg-config", 2247 | ] 2248 | -------------------------------------------------------------------------------- /rust-web-scraper/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rust-web-scraper" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | actix-web = "4.0.0-beta.9" 8 | reqwest = { version = "0.11", features = ["json"] } 9 | scraper = "0.12" 10 | tokio = { version = "1", features = ["full"] } 11 | serde = { version = "1.0", features = ["derive"] } 12 | serde_json = "1.0" -------------------------------------------------------------------------------- /rust-web-scraper/README.md: -------------------------------------------------------------------------------- 1 | #### Introduction 2 | Today, we're going to build a web scraper using Rust with the Actix Web framework to fetch data from websites. We'll create an endpoint that, when hit, scrapes a predefined website for information and returns it in JSON format. This project will introduce you to asynchronous programming, HTTP requests, and HTML parsing in Rust. 3 | 4 | #### Difficulty 5 | 🍂 **Intermediate** 6 | 7 | #### Prerequisites 8 | - Basic understanding of Rust 9 | - Familiarity with HTTP requests 10 | - Concept of asynchronous programming 11 | 12 | #### Project Structure 13 | Let's set up our project: 14 | 15 | ```sh 16 | mkdir rust-web-scraper 17 | cd rust-web-scraper 18 | cargo init --lib 19 | cargo add actix-web reqwest scraper tokio serde serde_json 20 | ``` 21 | 22 | Our folder structure: 23 | 24 | ``` 25 | rust-web-scraper/ 26 | │ 27 | ├── src/ 28 | │ ├── main.rs 29 | │ ├── scraper.rs 30 | │ └── models.rs 31 | │ 32 | ├── Cargo.toml 33 | └── README.md 34 | ``` 35 | 36 | #### Step 1: Setting up `Cargo.toml` 37 | 38 | ```toml 39 | [package] 40 | name = "web_scraper" 41 | version = "0.1.0" 42 | edition = "2018" 43 | 44 | [dependencies] 45 | actix-web = "4.0.0-beta.9" 46 | reqwest = { version = "0.11", features = ["json"] } 47 | scraper = "0.12" 48 | tokio = { version = "1", features = ["full"] } 49 | serde = { version = "1.0", features = ["derive"] } 50 | serde_json = "1.0" 51 | ``` 52 | 53 | #### Step 2: `models.rs` - Define the structure of our scraped data 54 | 55 | ```rust 56 | use serde::Serialize; 57 | 58 | #[derive(Serialize)] 59 | pub struct Article { 60 | pub title: String, 61 | pub link: String, 62 | } 63 | ``` 64 | 65 | #### Step 3: `scraper.rs` - Implement the scraping logic 66 | 67 | ```rust 68 | use reqwest; 69 | use scraper::{Html, Selector}; 70 | use crate::models::Article; 71 | 72 | pub async fn scrape_articles() -> Result, Box> { 73 | let resp = reqwest::get("https://example.com").await?.text().await?; 74 | let document = Html::parse_document(&resp); 75 | let selector = Selector::parse("article").unwrap(); 76 | let articles = document.select(&selector).filter_map(|article| { 77 | let title = article.select(&Selector::parse("h2").unwrap()).next()?.text().collect::(); 78 | let link = article.select(&Selector::parse("a").unwrap()).next()?.value().attr("href")?.to_string(); 79 | Some(Article { title, link }) 80 | }).collect(); 81 | Ok(articles) 82 | } 83 | ``` 84 | 85 | #### Step 4: `main.rs` - Set up the web server and route 86 | 87 | ```rust 88 | use actix_web::{web, App, HttpServer, Responder, HttpResponse}; 89 | use serde_json::json; 90 | mod scraper; 91 | mod models; 92 | 93 | #[actix_web::main] 94 | async fn main() -> std::io::Result<()> { 95 | HttpServer::new(|| { 96 | App::new() 97 | .route("/", web::get().to(get_articles)) 98 | }) 99 | .bind("127.0.0.1:8080")? 100 | .run() 101 | .await 102 | } 103 | 104 | async fn get_articles() -> impl Responder { 105 | match scraper::scrape_articles().await { 106 | Ok(articles) => HttpResponse::Ok().json(json!({ "articles": articles })), 107 | Err(_) => HttpResponse::InternalServerError().body("Failed to retrieve articles"), 108 | } 109 | } 110 | ``` 111 | 112 | #### Step 5: Usage 113 | 114 | To run your scraper: 115 | 116 | ```sh 117 | cargo run 118 | ``` 119 | 120 | Then, in your web browser, navigate to `http://127.0.0.1:8080/` or use any HTTP client like `curl` to get the scraped data: 121 | 122 | ```sh 123 | curl http://127.0.0.1:8080/ 124 | ``` 125 | 126 | #### Explanation 127 | 128 | - **Dependencies**: We use `actix-web` for the web server, `reqwest` for making HTTP requests, `scraper` for parsing HTML, `tokio` for async runtime, and `serde` for JSON serialization. 129 | - **Scraping Logic**: The `scrape_articles` function fetches the page content, parses it into an HTML document, and extracts article titles and links using CSS selectors. 130 | - **Web Server**: We set up an Actix Web server that listens on port 8080 for GET requests to the root path, which triggers the scraping and returns JSON. 131 | 132 | #### Conclusion 133 | 134 | This project teaches you how to create a web service that can scrape data from websites, handle asynchronous operations, and serve JSON data. It's a fantastic way to learn about Rust's concurrency model and web development capabilities: 135 | 136 | - **Extend the Project**: You can enhance it by adding more complex scraping logic, error handling, or by making the site to scrape configurable. 137 | - **Security Considerations**: Always respect the terms of service of websites you're scraping, implement rate limiting, and avoid overloading the target server. 138 | 139 | By following this guide, you've not only built a functional web scraper but also gained insights into Rust's ecosystem for web technologies. -------------------------------------------------------------------------------- /rust-web-scraper/src/main.rs: -------------------------------------------------------------------------------- 1 | use actix_web::{web, App, HttpServer, Responder, HttpResponse}; 2 | use serde_json::json; 3 | mod scraper; 4 | mod models; 5 | 6 | #[actix_web::main] 7 | async fn main() -> std::io::Result<()> { 8 | HttpServer::new(|| { 9 | App::new() 10 | .route("/", web::get().to(get_articles)) 11 | }) 12 | .bind("127.0.0.1:8080")? 13 | .run() 14 | .await 15 | } 16 | 17 | async fn get_articles() -> impl Responder { 18 | match scraper::scrape_articles().await { 19 | Ok(articles) => HttpResponse::Ok().json(json!({ "articles": articles })), 20 | Err(_) => HttpResponse::InternalServerError().body("Failed to retrieve articles"), 21 | } 22 | } -------------------------------------------------------------------------------- /rust-web-scraper/src/models.rs: -------------------------------------------------------------------------------- 1 | use serde::Serialize; 2 | 3 | #[derive(Serialize)] 4 | pub struct Article { 5 | pub title: String, 6 | pub link: String, 7 | } 8 | -------------------------------------------------------------------------------- /rust-web-scraper/src/scraper.rs: -------------------------------------------------------------------------------- 1 | use reqwest; 2 | use scraper::{Html, Selector}; 3 | use crate::models::Article; 4 | 5 | pub async fn scrape_articles() -> Result, Box> { 6 | let resp = reqwest::get("https://example.com").await?.text().await?; 7 | let document = Html::parse_document(&resp); 8 | let selector = Selector::parse("article").unwrap(); 9 | let articles = document.select(&selector).filter_map(|article| { 10 | let title = article.select(&Selector::parse("h2").unwrap()).next()?.text().collect::(); 11 | let link = article.select(&Selector::parse("a").unwrap()).next()?.value().attr("href")?.to_string(); 12 | Some(Article { title, link }) 13 | }).collect(); 14 | Ok(articles) 15 | } -------------------------------------------------------------------------------- /rust_temperature_converter/.gitignore: -------------------------------------------------------------------------------- 1 | /target -------------------------------------------------------------------------------- /rust_temperature_converter/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rust_temperature_converter" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | eframe = "0.20.0" 8 | egui = "0.20.0" -------------------------------------------------------------------------------- /rust_temperature_converter/README.md: -------------------------------------------------------------------------------- 1 | ## Introduction 2 | 3 | Welcome to Day 2 of our Rust project journey! Today, we're stepping into the world of GUI applications with Rust. We'll create a Temperature Converter that allows users to convert between Celsius and Fahrenheit. We'll use `egui`, a simple yet powerful GUI library for Rust, for this project. 4 | 5 | ## Prerequisites 6 | 7 | - Rust installed on your system (`rustup` and `cargo`) 8 | - Basic knowledge of Rust syntax 9 | 10 | ## Project Setup 11 | 12 | ### Step 1: Create a New Rust Project 13 | 14 | Open your terminal and run: 15 | 16 | ```sh 17 | cargo new rust_temperature_converter --bin 18 | cd rust_temperature_converter 19 | ``` 20 | 21 | ### Step 2: Add Dependencies 22 | 23 | Edit your `Cargo.toml` to include: 24 | 25 | ```toml 26 | [dependencies] 27 | eframe = "0.20.0" 28 | egui = "0.20.0" 29 | ``` 30 | 31 | ### Step 3: Project Structure 32 | 33 | Your project should now look like this: 34 | 35 | ``` 36 | rust_temperature_converter/ 37 | ├── Cargo.lock 38 | ├── Cargo.toml 39 | └── src/ 40 | └── main.rs 41 | ``` 42 | 43 | ### Step 4: Implementing the Converter 44 | 45 | Now, replace the content in `src/main.rs` with: 46 | 47 | ```rust 48 | use eframe::egui; 49 | 50 | fn main() -> Result<(), eframe::Error> { 51 | let options = eframe::NativeOptions::default(); 52 | eframe::run_native( 53 | "Temperature Converter", 54 | options, 55 | Box::new(|_cc| Box::new(TempConverter::default())), 56 | ) 57 | } 58 | 59 | struct TempConverter { 60 | celsius: f32, 61 | fahrenheit: f32, 62 | } 63 | 64 | impl Default for TempConverter { 65 | fn default() -> Self { 66 | Self { 67 | celsius: 0.0, 68 | fahrenheit: 32.0, 69 | } 70 | } 71 | } 72 | 73 | impl eframe::App for TempConverter { 74 | fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { 75 | egui::CentralPanel::default().show(ctx, |ui| { 76 | ui.heading("Temperature Converter"); 77 | ui.separator(); 78 | 79 | ui.label("Celsius:"); 80 | ui.text_edit_singleline(&mut self.celsius.to_string()); 81 | 82 | if ui.button("Convert to Fahrenheit").clicked() { 83 | self.fahrenheit = self.celsius * 9.0 / 5.0 + 32.0; 84 | } 85 | 86 | ui.label("Fahrenheit:"); 87 | ui.text_edit_singleline(&mut self.fahrenheit.to_string()); 88 | 89 | if ui.button("Convert to Celsius").clicked() { 90 | self.celsius = (self.fahrenheit - 32.0) * 5.0 / 9.0; 91 | } 92 | }); 93 | } 94 | } 95 | ``` 96 | 97 | ## Explanation of the Code 98 | 99 | - **Main Function**: Here we set up the application window using `eframe::NativeOptions`. The `run_native` function starts the application with our `TempConverter` struct as the app state. 100 | 101 | - **TempConverter Struct**: This holds our state, namely `celsius` and `fahrenheit` temperatures. We implement `Default` for easy initialization. 102 | 103 | - **App Implementation**: We implement the `eframe::App` trait for `TempConverter`. The `update` method is where the UI logic resides: 104 | - We use `CentralPanel` for our main window content. 105 | - `text_edit_singleline` allows users to input or see temperature values. 106 | - Buttons trigger temperature conversions using the formulas: 107 | - Celsius to Fahrenheit: `(°C × 9/5) + 32` 108 | - Fahrenheit to Celsius: `(°F - 32) × 5/9` 109 | 110 | ## Running the Project 111 | 112 | - Save your `main.rs` file. 113 | - From the terminal, in the project directory, run: 114 | 115 | ```sh 116 | cargo run 117 | ``` 118 | 119 | You should now see a window appear with input fields for Celsius and Fahrenheit temperatures and buttons to convert between them. 120 | 121 | ## Conclusion 122 | 123 | Congratulations! You've now created a functional GUI in Rust. This project not only teaches GUI interaction but also basic temperature unit conversion. As you continue with Rust, consider exploring different GUI libraries or adding more features like Kelvin conversion or saving conversion history. 124 | 125 | Keep coding, and see you tomorrow for Day 3! 🚀 126 | 127 | --- 128 | -------------------------------------------------------------------------------- /rust_temperature_converter/src/main.rs: -------------------------------------------------------------------------------- 1 | use eframe::egui; 2 | 3 | fn main() -> Result<(), eframe::Error> { 4 | let options = eframe::NativeOptions::default(); 5 | eframe::run_native( 6 | "Temperature Converter", 7 | options, 8 | Box::new(|_cc| Box::new(TempConverter::default())), 9 | ) 10 | } 11 | 12 | struct TempConverter { 13 | celsius: f32, 14 | fahrenheit: f32, 15 | } 16 | 17 | impl Default for TempConverter { 18 | fn default() -> Self { 19 | Self { 20 | celsius: 0.0, 21 | fahrenheit: 32.0, 22 | } 23 | } 24 | } 25 | 26 | impl eframe::App for TempConverter { 27 | fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { 28 | egui::CentralPanel::default().show(ctx, |ui| { 29 | ui.heading("Temperature Converter"); 30 | ui.separator(); 31 | 32 | ui.label("Celsius:"); 33 | ui.text_edit_singleline(&mut self.celsius.to_string()); 34 | 35 | if ui.button("Convert to Fahrenheit").clicked() { 36 | self.fahrenheit = self.celsius * 9.0 / 5.0 + 32.0; 37 | } 38 | 39 | ui.label("Fahrenheit:"); 40 | ui.text_edit_singleline(&mut self.fahrenheit.to_string()); 41 | 42 | if ui.button("Convert to Celsius").clicked() { 43 | self.celsius = (self.fahrenheit - 32.0) * 5.0 / 9.0; 44 | } 45 | }); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /todo_list/.gitignore: -------------------------------------------------------------------------------- 1 | /target -------------------------------------------------------------------------------- /todo_list/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 = "todo_list" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /todo_list/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "todo_list" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | -------------------------------------------------------------------------------- /todo_list/README.md: -------------------------------------------------------------------------------- 1 | ### Introduction 2 | Welcome to the first day of our 7-day Rust project series! Today, we'll create a basic command-line todo list application. This project will familiarize you with Rust's basic syntax, file I/O, and command-line argument handling. 3 | 4 | ### Step 1: Setting Up Your Project 5 | 6 | 1. **Create a new Rust project:** 7 | ```bash 8 | cargo new todo_list 9 | cd todo_list 10 | ``` 11 | 12 | 2. **Folder Structure:** 13 | Your folder should now look like this: 14 | ``` 15 | todo_list/ 16 | ├── Cargo.toml 17 | └── src/ 18 | └── main.rs 19 | ``` 20 | 21 | ### Step 2: Writing the Main Function 22 | 23 | Open `src/main.rs` and start with the basics: 24 | 25 | ```rust 26 | use std::fs; 27 | use std::io::{Write, BufRead, BufReader}; 28 | 29 | fn main() { 30 | println!("Welcome to Rust Todo!"); 31 | 32 | // Load todos or initialize empty vector 33 | let mut todos = load_todos().unwrap_or_else(|_| Vec::new()); 34 | 35 | // Main loop for adding, listing, or removing todos 36 | loop { 37 | println!("\n1. Add Todo\n2. List Todos\n3. Remove Todo\n4. Quit"); 38 | let choice = read_line(); 39 | 40 | match choice.trim() { 41 | "1" => add_todo(&mut todos), 42 | "2" => list_todos(&todos), 43 | "3" => remove_todo(&mut todos), 44 | "4" => break, 45 | _ => println!("Invalid choice, try again."), 46 | } 47 | } 48 | 49 | // Save todos before exiting 50 | match save_todos(&todos) { 51 | Ok(_) => println!("Todos saved."), 52 | Err(e) => eprintln!("Failed to save todos: {}", e), 53 | } 54 | } 55 | 56 | // Helper functions will be defined here 57 | ``` 58 | 59 | ### Step 3: Implementing Helper Functions 60 | 61 | ```rust 62 | fn read_line() -> String { 63 | let mut input = String::new(); 64 | std::io::stdin().read_line(&mut input).expect("Failed to read line"); 65 | input.trim().to_string() 66 | } 67 | 68 | fn load_todos() -> std::io::Result> { 69 | let file = fs::File::open("todos.txt")?; 70 | let reader = BufReader::new(file); 71 | Ok(reader.lines().collect::, _>>()?) 72 | } 73 | 74 | fn save_todos(todos: &[String]) -> std::io::Result<()> { 75 | let mut file = fs::File::create("todos.txt")?; 76 | for todo in todos { 77 | writeln!(file, "{}", todo)?; 78 | } 79 | Ok(()) 80 | } 81 | 82 | fn add_todo(todos: &mut Vec) { 83 | println!("Enter todo: "); 84 | let todo = read_line(); 85 | if !todo.is_empty() { 86 | todos.push(todo); 87 | println!("Todo added!"); 88 | } 89 | } 90 | 91 | fn list_todos(todos: &[String]) { 92 | if todos.is_empty() { 93 | println!("No todos yet!"); 94 | } else { 95 | for (index, todo) in todos.iter().enumerate() { 96 | println!("{}. {}", index + 1, todo); 97 | } 98 | } 99 | } 100 | 101 | fn remove_todo(todos: &mut Vec) { 102 | list_todos(todos); 103 | if !todos.is_empty() { 104 | println!("Enter the number of the todo to remove:"); 105 | if let Ok(num) = read_line().parse::() { 106 | if num > 0 && num <= todos.len() { 107 | todos.remove(num - 1); 108 | println!("Todo removed."); 109 | } else { 110 | println!("Invalid number."); 111 | } 112 | } 113 | } 114 | } 115 | ``` 116 | 117 | ### Step 4: Running Your Project 118 | 119 | To run your project: 120 | 121 | ```bash 122 | cargo run 123 | ``` 124 | 125 | ### Explanation: 126 | 127 | - **Cargo.toml**: This file contains metadata for your project and dependencies (although for this project, we don't need any external crates). 128 | 129 | - **std::fs and std::io**: These modules are used for file operations (reading and writing todos) and handling input/output operations. 130 | 131 | - **main()**: The entry point of our application. It manages the loop for user interaction. 132 | 133 | - **CRUD Operations**: Our functions `add_todo`, `list_todos`, `remove_todo` handle creating, reading, and deleting todos respectively. The `save_todos` and `load_todos` functions persist the todos to a file. 134 | 135 | - **Error Handling**: Basic error handling is implemented with `.expect()` for simplicity, but in a real-world app, you'd want more robust error handling. 136 | 137 | This project introduces basic Rust concepts like ownership, borrowing, error handling with `Result`, file I/O, and basic CLI interactions. Enjoy building your first Rust project, and see you tomorrow for something a bit more graphical! 138 | 139 | --- 140 | -------------------------------------------------------------------------------- /todo_list/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::fs; 2 | use std::io::{BufRead, BufReader, Write}; 3 | 4 | fn main() { 5 | println!("Welcome to Rust Todo!"); 6 | 7 | // Load todos or initialize empty vector 8 | let mut todos = load_todos().unwrap_or_else(|_| Vec::new()); 9 | 10 | // Main loop for adding, listing, or removing todos 11 | loop { 12 | println!("\n1. Add Todo\n2. List Todos\n3. Remove Todo\n4. Quit"); 13 | let choice = read_line(); 14 | 15 | match choice.trim() { 16 | "1" => add_todo(&mut todos), 17 | "2" => list_todos(&todos), 18 | "3" => remove_todo(&mut todos), 19 | "4" => break, 20 | _ => println!("Invalid choice, try again."), 21 | } 22 | } 23 | 24 | // Save todos before exiting 25 | match save_todos(&todos) { 26 | Ok(_) => println!("Todos saved."), 27 | Err(e) => eprintln!("Failed to save todos: {}", e), 28 | } 29 | } 30 | 31 | // Helper functions will be defined here 32 | fn read_line() -> String { 33 | let mut input = String::new(); 34 | std::io::stdin() 35 | .read_line(&mut input) 36 | .expect("Failed to read line"); 37 | input.trim().to_string() 38 | } 39 | 40 | fn load_todos() -> std::io::Result> { 41 | let file = fs::File::open("todos.txt")?; 42 | let reader = BufReader::new(file); 43 | Ok(reader.lines().collect::, _>>()?) 44 | } 45 | 46 | fn save_todos(todos: &[String]) -> std::io::Result<()> { 47 | let mut file = fs::File::create("todos.txt")?; 48 | for todo in todos { 49 | writeln!(file, "{}", todo)?; 50 | } 51 | Ok(()) 52 | } 53 | 54 | fn add_todo(todos: &mut Vec) { 55 | println!("Enter todo: "); 56 | let todo = read_line(); 57 | if !todo.is_empty() { 58 | todos.push(todo); 59 | println!("Todo added!"); 60 | } 61 | } 62 | 63 | fn list_todos(todos: &[String]) { 64 | if todos.is_empty() { 65 | println!("No todos yet!"); 66 | } else { 67 | for (index, todo) in todos.iter().enumerate() { 68 | println!("{}. {}", index + 1, todo); 69 | } 70 | } 71 | } 72 | 73 | fn remove_todo(todos: &mut Vec) { 74 | list_todos(todos); 75 | if !todos.is_empty() { 76 | println!("Enter the number of the todo to remove:"); 77 | if let Ok(num) = read_line().parse::() { 78 | if num > 0 && num <= todos.len() { 79 | todos.remove(num - 1); 80 | println!("Todo removed."); 81 | } else { 82 | println!("Invalid number."); 83 | } 84 | } 85 | } 86 | } 87 | --------------------------------------------------------------------------------