Last active
September 28, 2025 03:14
-
-
Save jmcph4/3ba4e27238abf506c48ae42563c06f94 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| use std::fmt::Display; | |
| use clap::{Parser, Subcommand}; | |
| #[derive(Copy, Clone, Debug)] | |
| pub enum Cost { | |
| Fixed(f64), | |
| Proportion((f64, f64)), | |
| } | |
| impl Display for Cost { | |
| fn fmt( | |
| &self, | |
| f: &mut std::fmt::Formatter<'_>, | |
| ) -> Result<(), std::fmt::Error> { | |
| match self { | |
| Self::Fixed(x) => write!(f, "{x}"), | |
| Self::Proportion((tot, r)) => write!(f, "{}", tot * r), | |
| } | |
| } | |
| } | |
| #[derive(Copy, Clone, Debug)] | |
| pub struct Expense<'a> { | |
| pub name: &'a str, | |
| pub cost: Cost, | |
| } | |
| impl Display for Expense<'_> { | |
| fn fmt( | |
| &self, | |
| f: &mut std::fmt::Formatter<'_>, | |
| ) -> Result<(), std::fmt::Error> { | |
| write!(f, "{}: {}", self.name, self.cost) | |
| } | |
| } | |
| #[derive(Clone, Debug)] | |
| pub struct Expenses<'a>(pub Vec<Expense<'a>>); | |
| impl Expenses<'_> { | |
| pub fn total(&self) -> Expense<'_> { | |
| Expense { | |
| name: "TOTAL", | |
| cost: Cost::Fixed( | |
| self.0 | |
| .iter() | |
| .map(|expense| match expense.cost { | |
| Cost::Fixed(x) => x, | |
| Cost::Proportion((tot, r)) => tot * r, | |
| }) | |
| .sum(), | |
| ), | |
| } | |
| } | |
| } | |
| impl Display for Expenses<'_> { | |
| fn fmt( | |
| &self, | |
| f: &mut std::fmt::Formatter<'_>, | |
| ) -> Result<(), std::fmt::Error> { | |
| for expense in &self.0 { | |
| writeln!(f, "{expense}")?; | |
| } | |
| writeln!(f, "{}", self.total()) | |
| } | |
| } | |
| pub const DOWNPAYMENT_RATE: f64 = 0.1; | |
| pub const STAMP_DUTY_RATE: f64 = 0.035; | |
| pub const CONVEYANCY: f64 = 2_500.00; | |
| pub const CGT_RATE: f64 = 0.5; | |
| pub const COMMISSION_RATE: f64 = 0.03; | |
| #[derive(Clone, Debug)] | |
| pub struct SegmentedExpenses<'a> { | |
| pub buy: Expenses<'a>, | |
| pub hold: Expenses<'a>, | |
| pub sell: Expenses<'a>, | |
| } | |
| impl SegmentedExpenses<'_> { | |
| pub fn buy(&self) -> Expenses<'_> { | |
| self.buy.clone() | |
| } | |
| pub fn sell(&self) -> Expenses<'_> { | |
| self.sell.clone() | |
| } | |
| pub fn house(price: f64) -> Self { | |
| Self { | |
| buy: Expenses(vec![ | |
| Expense { | |
| name: "Downpayment", | |
| cost: Cost::Proportion((price, DOWNPAYMENT_RATE)), | |
| }, | |
| Expense { | |
| name: "Stamp Duty", | |
| cost: Cost::Proportion((price, STAMP_DUTY_RATE)), | |
| }, | |
| Expense { | |
| name: "Conveyancy", | |
| cost: Cost::Fixed(CONVEYANCY), | |
| }, | |
| ]), | |
| hold: Expenses(vec![]), /* TODO: placeholder */ | |
| sell: Expenses(vec![ | |
| Expense { | |
| name: "Capital Gains Tax", | |
| cost: Cost::Proportion((price, CGT_RATE)), | |
| }, | |
| Expense { | |
| name: "Commission", | |
| cost: Cost::Proportion((price, COMMISSION_RATE)), | |
| }, | |
| Expense { | |
| name: "Conveyancy", | |
| cost: Cost::Fixed(CONVEYANCY), | |
| }, | |
| ]), | |
| } | |
| } | |
| } | |
| #[derive(Copy, Clone, Debug, Parser)] | |
| pub struct Opts { | |
| #[command(subcommand)] | |
| command: Commands, | |
| } | |
| #[derive(Copy, Clone, Debug, Subcommand)] | |
| pub enum Commands { | |
| Buy { | |
| price: f64, | |
| }, | |
| Sell { | |
| price: f64, | |
| }, | |
| Roundtrip { | |
| buy_price: f64, | |
| sell_price: f64, | |
| years: f64, | |
| }, | |
| } | |
| fn main() { | |
| let opts = Opts::parse(); | |
| match opts.command { | |
| Commands::Buy { price } => { | |
| print!("{}", SegmentedExpenses::house(price).buy()) | |
| } | |
| Commands::Sell { price } => { | |
| print!("{}", SegmentedExpenses::house(price).sell()) | |
| } | |
| _ => todo!(), | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment