commit c9c479b8738b19de8fa2f6022097931689c123a5 Author: Alex Selimov Date: Mon Jun 29 21:19:32 2026 -0400 Initial commit with basic Date type done diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2f7896d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +target/ diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..aed3e29 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,82 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "anyhow" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3" + +[[package]] +name = "habitrs" +version = "0.1.0" +dependencies = [ + "anyhow", + "serde", +] + +[[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.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" +dependencies = [ + "proc-macro2", +] + +[[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 = "syn" +version = "2.0.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..3eb5e9f --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "habitrs" +version = "0.1.0" +edition = "2024" + +[dependencies] +anyhow = { version = "1.0.103", features = ["std"] } +serde = { version = "1.0.228", features = ["derive"] } diff --git a/README.md b/README.md new file mode 100644 index 0000000..2e22a07 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# Simple Rust habit tracker + +Simple Rust habit tracker intended to integrate with the greater RLOS ecosystem. diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..65880be --- /dev/null +++ b/src/lib.rs @@ -0,0 +1 @@ +pub mod model; diff --git a/src/model.rs b/src/model.rs new file mode 100644 index 0000000..0e9ee45 --- /dev/null +++ b/src/model.rs @@ -0,0 +1,102 @@ +use std::collections::HashSet; + +use anyhow::{anyhow, bail}; + +pub struct Date { + pub day: u8, + pub month: u8, + pub year: u16, +} + +impl Date { + /// Create a day from String. Expects strings in format '06-26-2026' + pub fn from_string(val: &str) -> anyhow::Result { + let mut parts = val.split("-"); + let month = parts + .next() + .ok_or(anyhow!("Month missing from date string"))? + .parse()?; + + if month > 12 || month == 0 { + bail!("Month should be > 0 and <= 12") + } + + let day = parts + .next() + .ok_or(anyhow!("Day missing from date string"))? + .parse()?; + + if day == 0 { + bail!("Day must be > 0") + } + + match month { + 2 => { + if day > 29 { + bail!("February can have a max of 29 days (on leap years)") + } + } + 4 | 6 | 9 | 11 => { + if day > 30 { + bail!("The provided month only has 30 days") + } + } + 1 | 3 | 5 | 7 | 8 | 10 | 12 => { + if day > 31 { + bail!("The provided month only has 31 days") + } + } + _ => unreachable!(), + } + + let year = parts + .next() + .ok_or(anyhow!("Year missing from date string"))? + .parse()?; + + Ok(Date { day, month, year }) + } +} + +pub struct Habit { + pub name: String, + pub days_completed: HashSet, +} + +#[cfg(test)] +mod test { + use crate::model::Date; + + #[test] + fn correct_date_gets_parsed() { + let date = Date::from_string("06-16-2026").unwrap(); + + assert_eq!(date.day, 16); + assert_eq!(date.month, 6); + assert_eq!(date.year, 2026); + + let date = Date::from_string("6-16-2026").unwrap(); + + assert_eq!(date.day, 16); + assert_eq!(date.month, 6); + assert_eq!(date.year, 2026); + + let date = Date::from_string("6-6-2026").unwrap(); + + assert_eq!(date.day, 6); + assert_eq!(date.month, 6); + assert_eq!(date.year, 2026); + } + + #[test] + fn bad_date_doesnt_get_parsed() { + let date = Date::from_string("-12-2026"); + assert!(date.is_err()); + + let date = Date::from_string("13-01-2026"); + assert!(date.is_err()); + + let date = Date::from_string("01-32-2026"); + assert!(date.is_err()); + } +}