1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
use std::fmt::{Display, Formatter};

use inquire::{
    error::{CustomUserError, InquireResult},
    required, CustomType, DateSelect, MultiSelect, Select, Text,
};

fn main() -> InquireResult<()> {
    let _date = DateSelect::new("Date:").prompt()?;

    let _category = Select::new("Category:", get_categories()).prompt()?;

    let _payee = Text::new("Payee:")
        .with_validator(required!("This field is required"))
        .with_autocomplete(&payee_suggestor)
        .with_help_message("e.g. Music Store")
        .with_page_size(5)
        .prompt()?;

    let amount: f64 = CustomType::new("Amount:")
        .with_formatter(&|i: f64| format!("${}", i))
        .with_error_message("Please type a valid number")
        .with_help_message("Type the amount in US dollars using a decimal point as a separator")
        .prompt()
        .unwrap();

    let _description = Text::new("Description:")
        .with_help_message("Optional notes")
        .prompt()?;

    let mut accounts = get_accounts();
    let accounts_mut = accounts.iter_mut().collect();
    let account = Select::new("Account:", accounts_mut).prompt()?;
    account.balance -= amount;

    let _tags = MultiSelect::new("Tags:", get_tags()).prompt()?;

    println!("Your transaction has been successfully recorded.");
    println!("The balance of {} is now ${:.2}", account, account.balance);

    Ok(())
}

/// This could be retrieved from a database, for example.
fn get_tags() -> Vec<&'static str> {
    vec![
        "august-surprise",
        "birthday-gifts",
        "cat-aurora",
        "christmas-gifts-2020",
        "dog-bob",
        "dog-russ",
        "new-zealand-jan-2020",
        "roma-oct-2021",
    ]
}

struct Account {
    name: &'static str,
    balance: f64,
}

impl Account {
    pub fn new(name: &'static str, balance: f64) -> Self {
        Self { name, balance }
    }
}

impl Display for Account {
    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
        write!(f, "{}", self.name)
    }
}

/// This could be retrieved from a database, for example.
fn get_accounts() -> Vec<Account> {
    vec![
        Account::new("401k", 1.00),
        Account::new("Cash", 10.00),
        Account::new("D40 Bank", 100.00),
        Account::new("D40 Bank Credit Card", 1000.00),
        Account::new("Digital Wallet", 100.00),
        Account::new("Established Bank", 10.00),
        Account::new("Investments Account", 1.00),
        Account::new("Meal Voucher", 10.00),
        Account::new("Mortgage", 100.00),
        Account::new("Zeus Bank Credit Card", 354.08),
    ]
}

/// This could be retrieved from a database, for example.
fn get_categories() -> Vec<&'static str> {
    vec![
        "Rent",
        "Energy",
        "Water",
        "Internet",
        "Phone",
        "Groceries",
        "Eating Out",
        "Transportation",
        "Gifts",
        "Clothes",
        "Home Appliances",
    ]
}

/// This could be faster by using smarter ways to check for matches, when dealing with larger datasets.
fn payee_suggestor(input: &str) -> Result<Vec<String>, CustomUserError> {
    let input = input.to_lowercase();

    Ok(get_existing_payees()
        .iter()
        .filter(|p| p.to_lowercase().contains(&input))
        .take(5)
        .map(|p| String::from(*p))
        .collect())
}

/// This could be retrieved from a database, for example.
fn get_existing_payees() -> &'static [&'static str] {
    &[
        "Armstrong-Jacobs",
        "Barrows-Becker",
        "Becker PLC",
        "Bins, Fritsch and Hartmann",
        "Feil PLC",
        "Frami-Fisher",
        "Goyette Group",
        "Heathcote PLC",
        "Hilpert-Kovacek",
        "Keebler Inc",
        "Kuhn-Rippin",
        "McGlynn LLC",
        "McKenzie, Kris and Yundt",
        "Medhurst, Conroy and Will",
        "Ruecker LLC",
        "Steuber, Casper and Hermann",
        "Torphy-Boyer",
        "Volkman, Smith and Shanahan",
        "VonRueden-Rath",
        "Waelchi and Sons",
    ]
}