-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.py
307 lines (263 loc) · 11.6 KB
/
main.py
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
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
import argparse
from tabulate import tabulate
import json
from datetime import date, datetime
import os
class Expense():
def __init__(self, id, amount, description, date=None) -> None:
self.amount = amount
self.id = id
self.description = description
self.date = date if date else datetime.today().strftime('%Y-%m-%d')
class Expense_manager():
def __init__(self, expenses_file) -> None:
self.expenses_file = expenses_file
self.expenses = self.load_expenses()
def load_expenses(self) -> list[Expense]:
try:
if not os.path.exists(self.expenses_file):
print(f"Creating new expenses file at {self.expenses_file}")
try:
# Create an empty expenses file with proper permissions
with open(self.expenses_file, 'x') as file:
json.dump([], file)
except (IOError, PermissionError) as e:
print(f"Error creating expenses file: {str(e)}")
return []
return []
with open(self.expenses_file, 'r') as file:
try:
expenses_data = json.load(file)
if not isinstance(expenses_data, list):
print("Warning: Expenses file contains invalid data format. Expected a list.")
return []
return [Expense(**expense) for expense in expenses_data]
except json.JSONDecodeError as e:
print(f"Error decoding JSON from expenses file: {str(e)}")
return []
except (KeyError, TypeError) as e:
print(f"Error parsing expense data: {str(e)}")
return []
except (IOError, PermissionError) as e:
print(f"Error accessing expenses file: {str(e)}")
return []
def save_expenses(self) -> None:
with open(self.expenses_file, 'w') as file:
json.dump([expense.__dict__ for expense in self.expenses], file, indent=4)
def get_id(self) -> int:
used_ids = [expense.id for expense in self.expenses]
id = 0
while id in used_ids:
id += 1
return id
def create_expense(self, amount, description) -> Expense:
id = self.get_id()
new_expense = Expense(id, amount, description)
self.expenses.append(new_expense)
self.save_expenses()
return new_expense
def delete_expense(self, id):
self.expenses = [expense for expense in self.expenses if expense.id != id]
self.save_expenses()
def list_expenses(self):
if not self.expenses:
print("No expenses to print")
return
headers = ["ID", "Date", "Description", "Amount"]
table = [[expense.id, expense.date, expense.description, expense.amount] for expense in self.expenses]
print(tabulate(table, headers, tablefmt='simple_outline'))
def month_summary(self, month):
sum = 0
for expense in self.expenses:
expense_month = datetime.strptime(expense.date, '%Y-%m-%d').month
if expense_month == month:
sum += int(expense.amount)
print(f'Total expenditure for the month: {sum}')
def update_expense(self, id, amount, description):
for expense in self.expenses:
if expense.id == id:
if amount is not None:
expense.amount = amount
if description is not None:
expense.description = description
break
self.save_expenses()
def clear_terminal():
os.system('clear')
def interactive_mode(expense_manager):
"""Interactive mode for the expense tracker.
Provides a menu-driven interface with all features available in CLI mode.
"""
while True:
clear_terminal()
print("\nExpense Tracker Menu:")
print("1. Add Expense")
print("2. List Expenses")
print("3. Monthly Summary")
print("4. Delete Expense")
print("5. Update Expense")
print("6. Total Summary")
print("7. Exit")
choice = input("\nEnter your choice (1-7): ")
if choice == "1":
clear_terminal()
description = input("Enter expense description: ")
while True:
try:
amount = float(input("Enter amount: "))
if amount <= 0:
print("Amount must be greater than zero.")
continue
break
except ValueError:
print("Please enter a valid number.")
expense = expense_manager.create_expense(amount, description)
print(f"\nExpense added successfully (ID: {expense.id})")
input("\nPress Enter to continue...")
elif choice == "2":
clear_terminal()
print("\nCurrent Expenses:\n")
expense_manager.list_expenses()
input("\nPress Enter to continue...")
elif choice == "3":
clear_terminal()
while True:
try:
month = int(input("Enter month (1-12): "))
if 1 <= month <= 12:
print()
expense_manager.month_summary(month)
break
print("Month must be between 1 and 12.")
except ValueError:
print("Please enter a valid number.")
input("\nPress Enter to continue...")
elif choice == "4":
clear_terminal()
if not expense_manager.expenses:
print("No expenses to delete.")
input("\nPress Enter to continue...")
continue
print("Current Expenses:\n")
expense_manager.list_expenses()
print()
while True:
try:
id = int(input("Enter ID of expense to delete: "))
if id in [expense.id for expense in expense_manager.expenses]:
expense_manager.delete_expense(id)
print("\nExpense deleted successfully")
break
print(f"No expense found with ID {id}")
except ValueError:
print("Please enter a valid number.")
input("\nPress Enter to continue...")
elif choice == "5":
clear_terminal()
if not expense_manager.expenses:
print("No expenses to update.")
input("\nPress Enter to continue...")
continue
print("Current Expenses:\n")
expense_manager.list_expenses()
print()
while True:
try:
id = int(input("Enter ID of expense to update: "))
if id not in [expense.id for expense in expense_manager.expenses]:
print(f"No expense found with ID {id}")
continue
break
except ValueError:
print("Please enter a valid number.")
print("\nLeave blank to keep current value")
description = input("Enter new description (or press Enter to skip): ").strip()
description = description if description else None
amount = None
amount_input = input("Enter new amount (or press Enter to skip): ").strip()
if amount_input:
try:
amount = float(amount_input)
if amount <= 0:
print("Amount must be greater than zero.")
input("\nPress Enter to continue...")
continue
except ValueError:
print("Invalid amount entered.")
input("\nPress Enter to continue...")
continue
if description is None and amount is None:
print("No changes provided.")
else:
expense_manager.update_expense(id, amount, description)
print("\nExpense updated successfully")
input("\nPress Enter to continue...")
elif choice == "6":
clear_terminal()
total = sum(float(expense.amount) for expense in expense_manager.expenses)
print(f"\nTotal expenses: ${total:.2f}")
input("\nPress Enter to continue...")
elif choice == "7":
clear_terminal()
print("Goodbye!")
break
else:
print("\nInvalid choice. Please try again.")
input("\nPress Enter to continue...")
def main():
parser = argparse.ArgumentParser(description='Expense Tracker CLI')
subparsers = parser.add_subparsers(dest='command', help='Commands')
# Add command
add_parser = subparsers.add_parser('add', help='Add a new expense')
add_parser.add_argument('--description', required=True, help='Description of the expense')
add_parser.add_argument('--amount', type=float, required=True, help='Amount of the expense')
# List command
subparsers.add_parser('list', help='List all expenses')
# Summary command
summary_parser = subparsers.add_parser('summary', help='Show expense summary')
summary_parser.add_argument('--month', type=int, help='Show summary for specific month')
# Delete command
delete_parser = subparsers.add_parser('delete', help='Delete an expense')
delete_parser.add_argument('--id', type=int, required=True, help='ID of the expense to delete')
# Update command
update_parser = subparsers.add_parser('update', help='Update an expense')
update_parser.add_argument('--id', type=int, required=True, help='ID of the expense to update')
update_parser.add_argument('--amount', type=float, help='New amount')
update_parser.add_argument('--description', help='New description')
args = parser.parse_args()
expense_manager = Expense_manager('expenses.json')
print(f'Current local time is: {datetime.now().isoformat()}')
if not args.command:
interactive_mode(expense_manager)
return
if args.command == 'add':
if args.amount <= 0:
print("Error: Amount must be greater than zero.")
return
expense = expense_manager.create_expense(args.amount, args.description)
print(f"Expense added successfully (ID: {expense.id})")
elif args.command == 'list':
if not expense_manager.expenses:
print("No expenses to list.")
return
expense_manager.list_expenses()
elif args.command == 'summary':
if not expense_manager.expenses:
print("No expenses recorded.")
return
if args.month:
if args.month < 1 or args.month > 12:
print("Error: Month must be between 1 and 12.")
return
expense_manager.month_summary(args.month)
else:
total = sum(float(expense.amount) for expense in expense_manager.expenses)
print(f"Total expenses: ${total:.2f}")
elif args.command == 'delete':
if args.id not in [expense.id for expense in expense_manager.expenses]:
print(f"Error: No expense found with ID {args.id}.")
return
expense_manager.delete_expense(args.id)
print("Expense deleted successfully")
if __name__ == '__main__':
main()