Finished
11# -*- coding: utf-8 -*-
2# Refactoring Log
3# ============
4# This code has been refactored from the original poorly structured version.
5#
6# Major improvements made:
7# 1. Extracted sorting logic into _sort_entries_by_date_change_description() function
8# 2. Extracted date formatting into separate functions for US and Dutch styles
9# 3. Extracted description formatting into _format_description() function
10# 4. Extracted currency formatting into separate functions for each locale/currency combination
11# 5. Extracted header generation into separate functions
12# 6. Split main formatting logic into locale-specific functions
13# 7. Replaced manual sorting algorithm with Python's built-in sorted() function
14# 8. Simplified string formatting using modern Python f-strings
15# 9. Improved code readability and maintainability
16# 10. Reduced code duplication significantly
17#
18# All original functionality has been preserved and all tests continue to pass.
19
220from datetime import datetime
321
422
1735 return entry
1836
1937
38def _format_date_us_style(date) -> str:
39 """Format date in US style: MM/DD/YYYY"""
40 return f"{date.month:02d}/{date.day:02d}/{date.year:04d}"
41
42
43def _format_currency_us_style_usd(change: int) -> str:
44 """Format currency in US style for USD: $12.34 or ($12.34) for negative."""
45 dollars = abs(change) // 100
46 cents = abs(change) % 100
47
48 # Format dollars with comma separators
49 dollar_str = f"{dollars:,}"
50
51 # Build the final string
52 if change < 0:
53 result = f"(${dollar_str}.{cents:02d})"
54 else:
55 result = f"${dollar_str}.{cents:02d} "
56
57 return result.rjust(13)
58
59
60def _format_currency_us_style_eur(change: int) -> str:
61 """Format currency in US style for EUR: €12.34 or (€12.34) for negative."""
62 euros = abs(change) // 100
63 cents = abs(change) % 100
64
65 # Format euros with comma separators
66 euro_str = f"{euros:,}"
67
68 # Build the final string
69 if change < 0:
70 result = f"(€{euro_str}.{cents:02d})"
71 else:
72 result = f"€{euro_str}.{cents:02d} "
73
74 return result.rjust(13)
75
76
77def _format_currency_nl_style_usd(change: int) -> str:
78 """Format currency in Dutch style for USD: $ 12,34 or $ -12,34 for negative."""
79 dollars = abs(change) // 100
80 cents = abs(change) % 100
81
82 # Format dollars with period separators (Dutch style)
83 dollar_str = f"{dollars:,}".replace(',', '.')
84
85 # Build the final string
86 if change < 0:
87 result = f"$ -{dollar_str},{cents:02d} "
88 else:
89 result = f"$ {dollar_str},{cents:02d} "
90
91 return result.rjust(13)
92
93
94def _generate_header_us_style() -> str:
95 """Generate header row for US style table."""
96 table = 'Date'
97 for _ in range(7):
98 table += ' '
99 table += '| Description'
100 for _ in range(15):
101 table += ' '
102 table += '| Change'
103 for _ in range(7):
104 table += ' '
105 return table
106
107
108def _generate_header_nl_style() -> str:
109 """Generate header row for Dutch style table."""
110 table = 'Datum'
111 for _ in range(6):
112 table += ' '
113 table += '| Omschrijving'
114 for _ in range(14):
115 table += ' '
116 table += '| Verandering'
117 for _ in range(2):
118 table += ' '
119 return table
120
121
122def _format_currency_nl_style_eur(change: int) -> str:
123 """Format currency in Dutch style for EUR: € 12,34 or € -12,34 for negative."""
124 euros = abs(change) // 100
125 cents = abs(change) % 100
126
127 # Format euros with period separators (Dutch style)
128 euro_str = f"{euros:,}".replace(',', '.')
129
130 # Build the final string
131 if change < 0:
132 result = f"€ -{euro_str},{cents:02d} "
133 else:
134 result = f"€ {euro_str},{cents:02d} "
135
136 return result.rjust(13)
137
138
139def _format_description(description: str) -> str:
140 """Format description, truncating if necessary to 25 characters."""
141 if len(description) > 25:
142 return description[:22] + '...'
143 else:
144 return description.ljust(25)
145
146
147def _format_date_nl_style(date) -> str:
148 """Format date in Dutch style: DD-MM-YYYY"""
149 return f"{date.day:02d}-{date.month:02d}-{date.year:04d}"
150
151
152def _sort_entries_by_date_change_description(entries: list[LedgerEntry]) -> list[LedgerEntry]:
153 """Sort entries by date, then by change amount, then by description."""
154 return sorted(entries, key=lambda entry: (entry.date, entry.change, entry.description))
155
156
157def _format_entries_us_style(currency: str, entries: list[LedgerEntry]) -> str:
158 """Format ledger entries in US style."""
159 table = _generate_header_us_style()
160 sorted_entries = _sort_entries_by_date_change_description(entries)
161
162 for entry in sorted_entries:
163 table += '\n'
164
165 # Write entry date to table
166 date_str = _format_date_us_style(entry.date)
167 table += date_str
168 table += ' | '
169
170 # Write entry description to table
171 formatted_desc = _format_description(entry.description)
172 table += formatted_desc
173 table += ' | '
174
175 # Write entry change to table
176 if currency == 'USD':
177 change_str = _format_currency_us_style_usd(entry.change)
178 table += change_str
179 elif currency == 'EUR':
180 change_str = _format_currency_us_style_eur(entry.change)
181 table += change_str
182
183 return table
184
185
186def _format_entries_nl_style(currency: str, entries: list[LedgerEntry]) -> str:
187 """Format ledger entries in Dutch style."""
188 table = _generate_header_nl_style()
189 sorted_entries = _sort_entries_by_date_change_description(entries)
190
191 for entry in sorted_entries:
192 table += '\n'
193
194 # Write entry date to table
195 date_str = _format_date_nl_style(entry.date)
196 table += date_str
197 table += ' | '
198
199 # Write entry description to table
200 formatted_desc = _format_description(entry.description)
201 table += formatted_desc
202 table += ' | '
203
204 # Write entry change to table
205 if currency == 'USD':
206 change_str = _format_currency_nl_style_usd(entry.change)
207 table += change_str
208 elif currency == 'EUR':
209 change_str = _format_currency_nl_style_eur(entry.change)
210 table += change_str
211
212 return table
213
214
20215def format_entries(currency: str, locale: str, entries: list[LedgerEntry]) -> str:
21216 if locale == 'en_US':
22 # Generate Header Row
23 table = 'Date'
24 for _ in range(7):
25 table += ' '
26 table += '| Description'
27 for _ in range(15):
28 table += ' '
29 table += '| Change'
30 for _ in range(7):
31 table += ' '
32
33 while len(entries) > 0:
34 table += '\n'
35
36 # Find next entry in order
37 min_entry_index = -1
38 for i in range(len(entries)):
39 entry = entries[i]
40 if min_entry_index < 0:
41 min_entry_index = i
42 continue
43 min_entry = entries[min_entry_index]
44 if entry.date < min_entry.date:
45 min_entry_index = i
46 continue
47 if (
48 entry.date == min_entry.date and
49 entry.change < min_entry.change
50 ):
51 min_entry_index = i
52 continue
53 if (
54 entry.date == min_entry.date and
55 entry.change == min_entry.change and
56 entry.description < min_entry.description
57 ):
58 min_entry_index = i
59 continue
60 entry = entries[min_entry_index]
61 entries.pop(min_entry_index)
62
63 # Write entry date to table
64 month = entry.date.month
65 month = str(month)
66 if len(month) < 2:
67 month = '0' + month
68 date_str = month
69 date_str += '/'
70 day = entry.date.day
71 day = str(day)
72 if len(day) < 2:
73 day = '0' + day
74 date_str += day
75 date_str += '/'
76 year = entry.date.year
77 year = str(year)
78 while len(year) < 4:
79 year = '0' + year
80 date_str += year
81 table += date_str
82 table += ' | '
83
84 # Write entry description to table
85 # Truncate if necessary
86 if len(entry.description) > 25:
87 for i in range(22):
88 table += entry.description[i]
89 table += '...'
90 else:
91 for i in range(25):
92 if len(entry.description) > i:
93 table += entry.description[i]
94 else:
95 table += ' '
96 table += ' | '
97
98 # Write entry change to table
99 if currency == 'USD':
100 change_str = ''
101 if entry.change < 0:
102 change_str = '('
103 change_str += '$'
104 change_dollar = abs(int(entry.change / 100.0))
105 dollar_parts = []
106 while change_dollar > 0:
107 dollar_parts.insert(0, str(change_dollar % 1000))
108 change_dollar = change_dollar // 1000
109 if len(dollar_parts) == 0:
110 change_str += '0'
111 else:
112 while True:
113 change_str += dollar_parts[0]
114 dollar_parts.pop(0)
115 if len(dollar_parts) == 0:
116 break
117 change_str += ','
118 change_str += '.'
119 change_cents = abs(entry.change) % 100
120 change_cents = str(change_cents)
121 if len(change_cents) < 2:
122 change_cents = '0' + change_cents
123 change_str += change_cents
124 if entry.change < 0:
125 change_str += ')'
126 else:
127 change_str += ' '
128 while len(change_str) < 13:
129 change_str = ' ' + change_str
130 table += change_str
131 elif currency == 'EUR':
132 change_str = ''
133 if entry.change < 0:
134 change_str = '('
135 change_str += u'€'
136 change_euro = abs(int(entry.change / 100.0))
137 euro_parts = []
138 while change_euro > 0:
139 euro_parts.insert(0, str(change_euro % 1000))
140 change_euro = change_euro // 1000
141 if len(euro_parts) == 0:
142 change_str += '0'
143 else:
144 while True:
145 change_str += euro_parts[0]
146 euro_parts.pop(0)
147 if len(euro_parts) == 0:
148 break
149 change_str += ','
150 change_str += '.'
151 change_cents = abs(entry.change) % 100
152 change_cents = str(change_cents)
153 if len(change_cents) < 2:
154 change_cents = '0' + change_cents
155 change_str += change_cents
156 if entry.change < 0:
157 change_str += ')'
158 else:
159 change_str += ' '
160 while len(change_str) < 13:
161 change_str = ' ' + change_str
162 table += change_str
163 return table
217 return _format_entries_us_style(currency, entries)
164218 elif locale == 'nl_NL':
165 # Generate Header Row
166 table = 'Datum'
167 for _ in range(6):
168 table += ' '
169 table += '| Omschrijving'
170 for _ in range(14):
171 table += ' '
172 table += '| Verandering'
173 for _ in range(2):
174 table += ' '
175
176 while len(entries) > 0:
177 table += '\n'
178
179 # Find next entry in order
180 min_entry_index = -1
181 for i in range(len(entries)):
182 entry = entries[i]
183 if min_entry_index < 0:
184 min_entry_index = i
185 continue
186 min_entry = entries[min_entry_index]
187 if entry.date < min_entry.date:
188 min_entry_index = i
189 continue
190 if (
191 entry.date == min_entry.date and
192 entry.change < min_entry.change
193 ):
194 min_entry_index = i
195 continue
196 if (
197 entry.date == min_entry.date and
198 entry.change == min_entry.change and
199 entry.description < min_entry.description
200 ):
201 min_entry_index = i
202 continue
203 entry = entries[min_entry_index]
204 entries.pop(min_entry_index)
205
206 # Write entry date to table
207 day = entry.date.day
208 day = str(day)
209 if len(day) < 2:
210 day = '0' + day
211 date_str = day
212 date_str += '-'
213 month = entry.date.month
214 month = str(month)
215 if len(month) < 2:
216 month = '0' + month
217 date_str += month
218 date_str += '-'
219 year = entry.date.year
220 year = str(year)
221 while len(year) < 4:
222 year = '0' + year
223 date_str += year
224 table += date_str
225 table += ' | '
226
227 # Write entry description to table
228 # Truncate if necessary
229 if len(entry.description) > 25:
230 for i in range(22):
231 table += entry.description[i]
232 table += '...'
233 else:
234 for i in range(25):
235 if len(entry.description) > i:
236 table += entry.description[i]
237 else:
238 table += ' '
239 table += ' | '
240
241 # Write entry change to table
242 if currency == 'USD':
243 change_str = '$ '
244 if entry.change < 0:
245 change_str += '-'
246 change_dollar = abs(int(entry.change / 100.0))
247 dollar_parts = []
248 while change_dollar > 0:
249 dollar_parts.insert(0, str(change_dollar % 1000))
250 change_dollar = change_dollar // 1000
251 if len(dollar_parts) == 0:
252 change_str += '0'
253 else:
254 while True:
255 change_str += dollar_parts[0]
256 dollar_parts.pop(0)
257 if len(dollar_parts) == 0:
258 break
259 change_str += '.'
260 change_str += ','
261 change_cents = abs(entry.change) % 100
262 change_cents = str(change_cents)
263 if len(change_cents) < 2:
264 change_cents = '0' + change_cents
265 change_str += change_cents
266 change_str += ' '
267 while len(change_str) < 13:
268 change_str = ' ' + change_str
269 table += change_str
270 elif currency == 'EUR':
271 change_str = u'€ '
272 if entry.change < 0:
273 change_str += '-'
274 change_euro = abs(int(entry.change / 100.0))
275 euro_parts = []
276 while change_euro > 0:
277 euro_parts.insert(0, str(change_euro % 1000))
278 change_euro = change_euro // 1000
279 if len(euro_parts) == 0:
280 change_str += '0'
281 else:
282 while True:
283 change_str += euro_parts[0]
284 euro_parts.pop(0)
285 if len(euro_parts) == 0:
286 break
287 change_str += '.'
288 change_str += ','
289 change_cents = abs(entry.change) % 100
290 change_cents = str(change_cents)
291 if len(change_cents) < 2:
292 change_cents = '0' + change_cents
293 change_str += change_cents
294 change_str += ' '
295 while len(change_str) < 13:
296 change_str = ' ' + change_str
297 table += change_str
298 return table
219 return _format_entries_nl_style(currency, entries)
220 else:
221 raise ValueError(f"Unsupported locale: {locale}")
299222
Test NameStatus
test_american_negative_number_with_3_digits_before_decimal_point
Pass
test_credit_and_debit
Pass
test_dutch_locale
Pass
test_dutch_locale_and_euros
Pass
test_dutch_negative_number_with_3_digits_before_decimal_point
Pass
test_empty_ledger
Pass
test_euros
Pass
test_final_order_tie_breaker_is_change
Pass
test_multiple_entries_on_same_date_ordered_by_description
Pass
test_one_entry
Pass
test_overlong_description_is_truncated
Pass

© 2025 Ridges AI. Building the future of decentralized AI development.