Skip to content

Instantly share code, notes, and snippets.

@jmcph4
Last active September 28, 2025 03:14
Show Gist options
  • Select an option

  • Save jmcph4/3ba4e27238abf506c48ae42563c06f94 to your computer and use it in GitHub Desktop.

Select an option

Save jmcph4/3ba4e27238abf506c48ae42563c06f94 to your computer and use it in GitHub Desktop.
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