From 232ecd97265d55e5dfb49defbf6c883e13b5fc35 Mon Sep 17 00:00:00 2001 From: qubzes Date: Mon, 9 Feb 2026 11:45:21 +0100 Subject: [PATCH 1/3] feat: submission for expense tracker and enum operations --- submissions/week-2/day-3/sshdopey/Cargo.lock | 7 + submissions/week-2/day-3/sshdopey/Cargo.toml | 6 + submissions/week-2/day-3/sshdopey/src/main.rs | 86 +++++ submissions/week-2/day-5/sshdopey/Cargo.lock | 306 ++++++++++++++++++ submissions/week-2/day-5/sshdopey/Cargo.toml | 9 + submissions/week-2/day-5/sshdopey/TODO.md | 2 + .../week-2/day-5/sshdopey/src/handlers.rs | 69 ++++ submissions/week-2/day-5/sshdopey/src/main.rs | 26 ++ .../week-2/day-5/sshdopey/src/models.rs | 109 +++++++ .../week-2/day-5/sshdopey/src/utils.rs | 118 +++++++ 10 files changed, 738 insertions(+) create mode 100644 submissions/week-2/day-3/sshdopey/Cargo.lock create mode 100644 submissions/week-2/day-3/sshdopey/Cargo.toml create mode 100644 submissions/week-2/day-3/sshdopey/src/main.rs create mode 100644 submissions/week-2/day-5/sshdopey/Cargo.lock create mode 100644 submissions/week-2/day-5/sshdopey/Cargo.toml create mode 100644 submissions/week-2/day-5/sshdopey/TODO.md create mode 100644 submissions/week-2/day-5/sshdopey/src/handlers.rs create mode 100644 submissions/week-2/day-5/sshdopey/src/main.rs create mode 100644 submissions/week-2/day-5/sshdopey/src/models.rs create mode 100644 submissions/week-2/day-5/sshdopey/src/utils.rs diff --git a/submissions/week-2/day-3/sshdopey/Cargo.lock b/submissions/week-2/day-3/sshdopey/Cargo.lock new file mode 100644 index 0000000..3896bd6 --- /dev/null +++ b/submissions/week-2/day-3/sshdopey/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "practice-enums" +version = "0.1.0" diff --git a/submissions/week-2/day-3/sshdopey/Cargo.toml b/submissions/week-2/day-3/sshdopey/Cargo.toml new file mode 100644 index 0000000..267ea57 --- /dev/null +++ b/submissions/week-2/day-3/sshdopey/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "practice-enums" +version = "0.1.0" +edition = "2024" + +[dependencies] diff --git a/submissions/week-2/day-3/sshdopey/src/main.rs b/submissions/week-2/day-3/sshdopey/src/main.rs new file mode 100644 index 0000000..1455611 --- /dev/null +++ b/submissions/week-2/day-3/sshdopey/src/main.rs @@ -0,0 +1,86 @@ +use std::env; +use std::io::{self, Write}; +use std::os::unix::process::CommandExt; +use std::process::{Command, exit}; +use std::thread::sleep; +use std::time::Duration; + +enum Actions { + Off, + Sleep, + Reboot, + Shutdown, + Hibernate, +} + +fn loading(message: &str) { + print!(" >> {}", message); + let _ = io::stdout().flush(); + + for _ in 0..3 { + sleep(Duration::from_millis(500)); + print!("."); + let _ = io::stdout().flush(); + } + println!(""); +} + +fn sleep_mode(message: &str) { + loading(message); + println!(" >> System is asleep. Press Enter to wake up..."); + + let mut buffer = String::new(); + let _ = io::stdin().read_line(&mut buffer); + println!(" >> System awake!"); +} +fn shutdown(message: &str) { + loading(message); + exit(0); +} + +fn reboot(message: &str) { + loading(message); + if let Ok(current_exe) = env::current_exe() { + let _ = Command::new(current_exe).exec(); + } +} + +fn execute(action: Actions) { + match action { + Actions::Off => shutdown("Powering off"), + Actions::Shutdown => shutdown("Shutting down"), + Actions::Sleep => sleep_mode("Entering sleep mode"), + Actions::Reboot => reboot("System rebooting"), + Actions::Hibernate => sleep_mode("Hibernating system"), + } +} + +fn main() { + println!("\n --- SYSTEM READY ---"); + println!(" >> Please enter a command (Off, Sleep, Reboot, Shutdown, Hibernate):"); + + loop { + print!(" >> "); + let _ = io::stdout().flush(); + + let mut input = String::new(); + if io::stdin().read_line(&mut input).is_err() { + break; + } + + input = input.trim().to_lowercase(); + + if input.is_empty() { + continue; + } + + match input.as_str() { + "off" => execute(Actions::Off), + "sleep" => execute(Actions::Sleep), + "reboot" => execute(Actions::Reboot), + "shutdown" => execute(Actions::Shutdown), + "hibernate" => execute(Actions::Hibernate), + _ => println!(" >> Error: '{}' is not a recognized command.\n", input), + }; + } +} diff --git a/submissions/week-2/day-5/sshdopey/Cargo.lock b/submissions/week-2/day-5/sshdopey/Cargo.lock new file mode 100644 index 0000000..0595122 --- /dev/null +++ b/submissions/week-2/day-5/sshdopey/Cargo.lock @@ -0,0 +1,306 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "comfy-table" +version = "7.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "958c5d6ecf1f214b4c2bbbbf6ab9523a864bd136dcf71a7e8904799acfe1ad47" +dependencies = [ + "crossterm", + "unicode-segmentation", + "unicode-width", +] + +[[package]] +name = "crossterm" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" +dependencies = [ + "bitflags", + "crossterm_winapi", + "document-features", + "parking_lot", + "rustix", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "expense-tracker" +version = "0.1.0" +dependencies = [ + "comfy-table", + "serde", + "serde_json", +] + +[[package]] +name = "itoa" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" + +[[package]] +name = "libc" +version = "0.2.180" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" + +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "rustix" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "syn" +version = "2.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "zmij" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff05f8caa9038894637571ae6b9e29466c1f4f829d26c9b28f869a29cbe3445" diff --git a/submissions/week-2/day-5/sshdopey/Cargo.toml b/submissions/week-2/day-5/sshdopey/Cargo.toml new file mode 100644 index 0000000..66182c8 --- /dev/null +++ b/submissions/week-2/day-5/sshdopey/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "expense-tracker" +version = "0.1.0" +edition = "2024" + +[dependencies] +comfy-table = "7.2.2" +serde = { version = "1.0.228", features = ["derive"] } +serde_json = "1.0.149" diff --git a/submissions/week-2/day-5/sshdopey/TODO.md b/submissions/week-2/day-5/sshdopey/TODO.md new file mode 100644 index 0000000..8f84c6a --- /dev/null +++ b/submissions/week-2/day-5/sshdopey/TODO.md @@ -0,0 +1,2 @@ +you can update all you want +You can sign total expenses \ No newline at end of file diff --git a/submissions/week-2/day-5/sshdopey/src/handlers.rs b/submissions/week-2/day-5/sshdopey/src/handlers.rs new file mode 100644 index 0000000..6f90591 --- /dev/null +++ b/submissions/week-2/day-5/sshdopey/src/handlers.rs @@ -0,0 +1,69 @@ +use crate::models::ExpenseTracker; +use crate::utils::{ + print_expense, print_expenses_table, read_f64, read_input, read_transaction_type, read_u32, +}; + +pub fn handle_add(tracker: &mut ExpenseTracker) { + let name = read_input("Enter Name: ", None).unwrap(); + let amount = read_f64("Enter Amount: ", None).unwrap(); + let tx_type = read_transaction_type(None).unwrap(); + + let expense = tracker.add(name, amount, tx_type); + print_expense(&expense); + println!("Transaction added successfully."); +} + +pub fn handle_view(tracker: &ExpenseTracker) { + let expenses = tracker.get_all_expenses(); + if expenses.is_empty() { + println!("No expenses found."); + } else { + print_expenses_table(&expenses); + } +} + +pub fn handle_update(tracker: &mut ExpenseTracker) { + let id = read_u32("Enter ID to update: "); + + if let Some(expense) = tracker.get_expense(id) { + print_expense(expense); + println!("Press Enter to skip a field."); + + let new_name = read_input("New Name: ", Some(true)); + let new_amount = read_f64("New Amount: ", Some(true)); + let new_tx_type = read_transaction_type(Some(true)); + + if new_name.is_none() && new_amount.is_none() && new_tx_type.is_none() { + println!("No changes made."); + return; + } + + if let Some(updated) = tracker.update(id, new_name, new_amount, new_tx_type) { + print_expense(&updated); + println!("Transaction updated successfully."); + } else { + println!("Error updating transaction."); + } + } else { + println!("Error: No transaction found with ID {}.", id); + } +} + +pub fn handle_delete(tracker: &mut ExpenseTracker) { + let id = read_u32("Enter ID to delete: "); + + if let Some(deleted) = tracker.delete(id) { + print_expense(&deleted); + println!("Transaction deleted successfully."); + } else { + println!("Error: No transaction found with ID {}.", id); + } +} + +pub fn handle_quit() { + let confirm = read_input("Are you sure you want to quit? (y/n): ", None).unwrap(); + if confirm.eq_ignore_ascii_case("y") { + println!("Data saved. Exiting application."); + std::process::exit(0); + } +} diff --git a/submissions/week-2/day-5/sshdopey/src/main.rs b/submissions/week-2/day-5/sshdopey/src/main.rs new file mode 100644 index 0000000..82a56d0 --- /dev/null +++ b/submissions/week-2/day-5/sshdopey/src/main.rs @@ -0,0 +1,26 @@ +mod handlers; +mod models; +mod utils; + +use crate::utils::read_input; +use handlers::{handle_add, handle_delete, handle_quit, handle_update, handle_view}; +use models::ExpenseTracker; + +fn main() { + let mut tracker = ExpenseTracker::load(); + println!("--- Expense Tracker CLI ---"); + + loop { + println!("\nOPTIONS: [1] Add [2] View [3] Update [4] Delete [q] Quit"); + let choice = read_input("> ", None).unwrap(); + + match choice.as_str() { + "1" => handle_add(&mut tracker), + "2" => handle_view(&tracker), + "3" => handle_update(&mut tracker), + "4" => handle_delete(&mut tracker), + "q" => handle_quit(), + _ => println!("Invalid option. Please try again."), + } + } +} diff --git a/submissions/week-2/day-5/sshdopey/src/models.rs b/submissions/week-2/day-5/sshdopey/src/models.rs new file mode 100644 index 0000000..07a0038 --- /dev/null +++ b/submissions/week-2/day-5/sshdopey/src/models.rs @@ -0,0 +1,109 @@ +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::fmt; +use std::fs; + +const DATA_FILE: &str = "expenses.json"; + +#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq)] +pub enum TransactionType { + Credit, + Debit, +} + +impl fmt::Display for TransactionType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + TransactionType::Credit => write!(f, "Credit"), + TransactionType::Debit => write!(f, "Debit"), + } + } +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct Expense { + pub id: u32, + pub name: String, + pub amount: f64, + pub tx_type: TransactionType, +} + +#[derive(Serialize, Deserialize)] +pub struct ExpenseTracker { + records: HashMap, + next_id: u32, +} + +impl ExpenseTracker { + pub fn load() -> Self { + if let Ok(data) = fs::read_to_string(DATA_FILE) { + if let Ok(tracker) = serde_json::from_str(&data) { + return tracker; + } + } + Self { + records: HashMap::new(), + next_id: 1, + } + } + + pub fn save(&self) { + let json = serde_json::to_string_pretty(self).unwrap(); + fs::write(DATA_FILE, json).expect("Unable to write file"); + } + + pub fn add(&mut self, name: String, amount: f64, tx_type: TransactionType) -> Expense { + let expense = Expense { + id: self.next_id, + name, + amount, + tx_type, + }; + self.records.insert(expense.id, expense.clone()); + self.next_id += 1; + self.save(); + expense + } + + pub fn delete(&mut self, id: u32) -> Option { + let removed = self.records.remove(&id); + if removed.is_some() { + self.save(); + } + removed + } + + pub fn update( + &mut self, + id: u32, + name: Option, + amount: Option, + tx_type: Option, + ) -> Option { + if let Some(exp) = self.records.get_mut(&id) { + if let Some(n) = name { + exp.name = n; + } + if let Some(a) = amount { + exp.amount = a; + } + if let Some(t) = tx_type { + exp.tx_type = t; + } + let updated = exp.clone(); + self.save(); + return Some(updated); + } + None + } + + pub fn get_expense(&self, id: u32) -> Option<&Expense> { + self.records.get(&id) + } + + pub fn get_all_expenses(&self) -> Vec<&Expense> { + let mut expenses: Vec<&Expense> = self.records.values().collect(); + expenses.sort_by_key(|e| e.id); + expenses + } +} diff --git a/submissions/week-2/day-5/sshdopey/src/utils.rs b/submissions/week-2/day-5/sshdopey/src/utils.rs new file mode 100644 index 0000000..dda253c --- /dev/null +++ b/submissions/week-2/day-5/sshdopey/src/utils.rs @@ -0,0 +1,118 @@ +use crate::models::TransactionType; +use std::io::{self, Write}; + +use crate::models::Expense; +use comfy_table::{Attribute, Cell, Color, ContentArrangement, Table, presets::UTF8_FULL}; + +pub fn print(s: &str) { + print!("{}", s); + io::stdout().flush().unwrap(); +} + +pub fn read_input(prompt: &str, is_optional: Option) -> Option { + print(prompt); + let mut input = String::new(); + io::stdin() + .read_line(&mut input) + .expect("Failed to read input"); + let trimmed = input.trim().to_string(); + + if is_optional.unwrap_or(false) && trimmed.is_empty() { + None + } else { + Some(trimmed) + } +} + +pub fn read_f64(prompt: &str, is_optional: Option) -> Option { + loop { + let input = read_input(prompt, is_optional); + match input { + None => return None, + Some(s) if s.is_empty() => println!("Please enter a valid amount."), + Some(s) => match s.parse::() { + Ok(n) => return Some(n), + Err(_) => println!("Invalid number. Please enter a valid amount."), + }, + } + } +} + +pub fn read_u32(prompt: &str) -> u32 { + loop { + let input = read_input(prompt, None).unwrap(); + match input.parse::() { + Ok(n) => return n, + Err(_) => println!("Invalid ID. Please enter a valid positive number."), + } + } +} + +pub fn read_transaction_type(is_optional: Option) -> Option { + loop { + let input = read_input("Type (c = Credit, d = Debit): ", is_optional); + match input { + None => return None, + Some(s) => match s.as_str() { + "c" | "C" | "credit" | "Credit" => return Some(TransactionType::Credit), + "d" | "D" | "debit" | "Debit" => return Some(TransactionType::Debit), + _ => println!("Invalid input. Please enter 'c' for Credit or 'd' for Debit."), + }, + } + } +} + +pub fn print_expense(exp: &Expense) { + let color = match exp.tx_type { + TransactionType::Credit => Color::Green, + TransactionType::Debit => Color::Red, + }; + let mut table = Table::new(); + table + .load_preset(UTF8_FULL) + .set_content_arrangement(ContentArrangement::Dynamic); + table.add_row(vec![ + Cell::new(exp.id), + Cell::new(&exp.name).add_attribute(Attribute::Bold), + Cell::new(format!("${:.2}", exp.amount)), + Cell::new(format!("{}", exp.tx_type)).fg(color), + ]); + println!("{table}"); +} + +pub fn print_expenses_table(expenses: &[&Expense]) { + let mut table = Table::new(); + table + .load_preset(UTF8_FULL) + .set_content_arrangement(ContentArrangement::Dynamic) + .set_header(vec!["ID", "Name", "Amount", "Type"]); + + let mut total_credit: f64 = 0.0; + let mut total_debit: f64 = 0.0; + + for exp in expenses { + let color = match exp.tx_type { + TransactionType::Credit => { + total_credit += exp.amount; + Color::Green + } + TransactionType::Debit => { + total_debit += exp.amount; + Color::Red + } + }; + + table.add_row(vec![ + Cell::new(exp.id), + Cell::new(&exp.name).add_attribute(Attribute::Bold), + Cell::new(format!("${:.2}", exp.amount)), + Cell::new(format!("{}", exp.tx_type)).fg(color), + ]); + } + println!("{table}"); + + let net = total_credit - total_debit; + println!("Total Credit: +${:.2}", total_credit); + println!("Total Debit: -${:.2}", total_debit); + println!("Net Balance: {:+.2}", net); +} From 79b253ac986edd863ec7247200034a54fa5eef69 Mon Sep 17 00:00:00 2001 From: qubzes Date: Mon, 9 Feb 2026 11:45:54 +0100 Subject: [PATCH 2/3] chore: clear TODO.md --- submissions/week-2/day-5/sshdopey/TODO.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/submissions/week-2/day-5/sshdopey/TODO.md b/submissions/week-2/day-5/sshdopey/TODO.md index 8f84c6a..e69de29 100644 --- a/submissions/week-2/day-5/sshdopey/TODO.md +++ b/submissions/week-2/day-5/sshdopey/TODO.md @@ -1,2 +0,0 @@ -you can update all you want -You can sign total expenses \ No newline at end of file From 6cbc6685fb70d73b9c8ad4f10888686e479d655c Mon Sep 17 00:00:00 2001 From: qubzes Date: Mon, 9 Feb 2026 12:44:48 +0100 Subject: [PATCH 3/3] refactor: input functions from `Option` to `bool`, update quit message and expect >>> unwrap --- .../week-2/day-5/sshdopey/src/handlers.rs | 16 ++++++++-------- submissions/week-2/day-5/sshdopey/src/main.rs | 2 +- submissions/week-2/day-5/sshdopey/src/models.rs | 2 +- submissions/week-2/day-5/sshdopey/src/utils.rs | 10 +++++----- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/submissions/week-2/day-5/sshdopey/src/handlers.rs b/submissions/week-2/day-5/sshdopey/src/handlers.rs index 6f90591..d8cecf4 100644 --- a/submissions/week-2/day-5/sshdopey/src/handlers.rs +++ b/submissions/week-2/day-5/sshdopey/src/handlers.rs @@ -4,9 +4,9 @@ use crate::utils::{ }; pub fn handle_add(tracker: &mut ExpenseTracker) { - let name = read_input("Enter Name: ", None).unwrap(); - let amount = read_f64("Enter Amount: ", None).unwrap(); - let tx_type = read_transaction_type(None).unwrap(); + let name = read_input("Enter Name: ", false).unwrap(); + let amount = read_f64("Enter Amount: ", false).unwrap(); + let tx_type = read_transaction_type(false).unwrap(); let expense = tracker.add(name, amount, tx_type); print_expense(&expense); @@ -29,9 +29,9 @@ pub fn handle_update(tracker: &mut ExpenseTracker) { print_expense(expense); println!("Press Enter to skip a field."); - let new_name = read_input("New Name: ", Some(true)); - let new_amount = read_f64("New Amount: ", Some(true)); - let new_tx_type = read_transaction_type(Some(true)); + let new_name = read_input("New Name: ", true); + let new_amount = read_f64("New Amount: ", true); + let new_tx_type = read_transaction_type(true); if new_name.is_none() && new_amount.is_none() && new_tx_type.is_none() { println!("No changes made."); @@ -61,9 +61,9 @@ pub fn handle_delete(tracker: &mut ExpenseTracker) { } pub fn handle_quit() { - let confirm = read_input("Are you sure you want to quit? (y/n): ", None).unwrap(); + let confirm = read_input("Are you sure you want to quit? (y/n): ", false).unwrap(); if confirm.eq_ignore_ascii_case("y") { - println!("Data saved. Exiting application."); + println!("Exiting application"); std::process::exit(0); } } diff --git a/submissions/week-2/day-5/sshdopey/src/main.rs b/submissions/week-2/day-5/sshdopey/src/main.rs index 82a56d0..206d5c8 100644 --- a/submissions/week-2/day-5/sshdopey/src/main.rs +++ b/submissions/week-2/day-5/sshdopey/src/main.rs @@ -12,7 +12,7 @@ fn main() { loop { println!("\nOPTIONS: [1] Add [2] View [3] Update [4] Delete [q] Quit"); - let choice = read_input("> ", None).unwrap(); + let choice = read_input("> ", false).unwrap(); match choice.as_str() { "1" => handle_add(&mut tracker), diff --git a/submissions/week-2/day-5/sshdopey/src/models.rs b/submissions/week-2/day-5/sshdopey/src/models.rs index 07a0038..6f0d5df 100644 --- a/submissions/week-2/day-5/sshdopey/src/models.rs +++ b/submissions/week-2/day-5/sshdopey/src/models.rs @@ -48,7 +48,7 @@ impl ExpenseTracker { } pub fn save(&self) { - let json = serde_json::to_string_pretty(self).unwrap(); + let json = serde_json::to_string_pretty(self).expect("Failed to serialize data"); fs::write(DATA_FILE, json).expect("Unable to write file"); } diff --git a/submissions/week-2/day-5/sshdopey/src/utils.rs b/submissions/week-2/day-5/sshdopey/src/utils.rs index dda253c..0200a95 100644 --- a/submissions/week-2/day-5/sshdopey/src/utils.rs +++ b/submissions/week-2/day-5/sshdopey/src/utils.rs @@ -9,7 +9,7 @@ pub fn print(s: &str) { io::stdout().flush().unwrap(); } -pub fn read_input(prompt: &str, is_optional: Option) -> Option { +pub fn read_input(prompt: &str, is_optional: bool) -> Option { print(prompt); let mut input = String::new(); io::stdin() @@ -17,14 +17,14 @@ pub fn read_input(prompt: &str, is_optional: Option) -> Option { .expect("Failed to read input"); let trimmed = input.trim().to_string(); - if is_optional.unwrap_or(false) && trimmed.is_empty() { + if is_optional && trimmed.is_empty() { None } else { Some(trimmed) } } -pub fn read_f64(prompt: &str, is_optional: Option) -> Option { +pub fn read_f64(prompt: &str, is_optional: bool) -> Option { loop { let input = read_input(prompt, is_optional); match input { @@ -40,7 +40,7 @@ pub fn read_f64(prompt: &str, is_optional: Option) -> Option { pub fn read_u32(prompt: &str) -> u32 { loop { - let input = read_input(prompt, None).unwrap(); + let input = read_input(prompt, false).unwrap(); match input.parse::() { Ok(n) => return n, Err(_) => println!("Invalid ID. Please enter a valid positive number."), @@ -48,7 +48,7 @@ pub fn read_u32(prompt: &str) -> u32 { } } -pub fn read_transaction_type(is_optional: Option) -> Option { +pub fn read_transaction_type(is_optional: bool) -> Option { loop { let input = read_input("Type (c = Credit, d = Debit): ", is_optional); match input {