Skip to content

Commit

Permalink
Introduce '--only-categories' option for list command
Browse files Browse the repository at this point in the history
- skip displaying of BaseEntries
- show percentage share in total expenses/earnings of each category
- close #33
  • Loading branch information
pylipp committed Jan 21, 2020
1 parent 32fd89d commit ac5874a
Show file tree
Hide file tree
Showing 6 changed files with 64 additions and 10 deletions.
1 change: 1 addition & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### Added
- Configuration.get_option() returns converted option value if an option was assigned a type (int, float, boolean). Available option types are also used for validating configuration. PluginConfiguration.init_option_types() is added to enable option typing in plugins. (#56)
- The output of the 'list' command shows the difference of total earnings and expenses. (#29)
- The output of the 'list' command shows only category entries incl. their percentage share in total listing values when the '--only-categories' option is specified. (#33)
### Changed
- The PluginConfiguration.validate() method is not required to be implemented in derived classes anymore.
- Instead of having Configuration.get_option() return a section if no option was specified, a method get_section() is introduced.
Expand Down
10 changes: 9 additions & 1 deletion financeager/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,10 @@ def _info(message):
"FRONTEND", "default_category")
if command == "list":
# Extract formatting options; irrelevant, event confusing for Server
for option in ["stacked_layout", "entry_sort", "category_sort"]:
for option in [
"stacked_layout", "entry_sort", "category_sort",
"only_categories"
]:
formatting_options[option] = params.pop(option)

exit_code = FAILURE
Expand Down Expand Up @@ -320,6 +323,11 @@ def _parse_command(args=None):
"--category-sort",
choices=["name", "value"],
default=financeager.DEFAULT_CATEGORY_ENTRY_SORT_KEY)
list_parser.add_argument(
"--only-categories",
action="store_true",
help="show only category entries incl. percentage",
)

periods_parser = subparsers.add_parser(
"periods", help="list all period databases")
Expand Down
20 changes: 17 additions & 3 deletions financeager/entries.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,24 +86,38 @@ def append(self, base_entry):
self.entries.append(base_entry)
self.value += base_entry.value

def string(self, *, entry_sort=DEFAULT_BASE_ENTRY_SORT_KEY):
def string(self,
*,
entry_sort=DEFAULT_BASE_ENTRY_SORT_KEY,
total_listing_value=None):
"""Return a formatted string representing the entry including its
children (i.e. BaseEntries). The category representation is supposed
to be longer than the BaseEntry representation so that the latter is
indented.
'entry_sort' determines the property according to which the list of base
entries is sorted.
If 'total_listing_value' is set, BaseEntries are not listed but instead
the share in the total listing of the category is inserted.
"""
percent = ""
if total_listing_value is not None:
percent = "{1:>{0}.1f}".format(
BaseEntry.DATE_LENGTH, 100 * self.value / total_listing_value)

lines = [
"{name:{0}.{0}} {value:>{1}.{2}f}".format(
"{name:{0}.{0}} {value:>{1}.{2}f} {percent}".format(
self.NAME_LENGTH,
BaseEntry.VALUE_LENGTH,
BaseEntry.VALUE_DIGITS,
name=self.name.title(),
value=self.value).ljust(self.TOTAL_LENGTH)
value=self.value,
percent=percent,
).ljust(self.TOTAL_LENGTH)
]

if total_listing_value is not None:
return lines[0]

sort_key = lambda e: getattr(e, entry_sort)
for entry in sorted(self.entries, key=sort_key):
lines.append(self.BASE_ENTRY_INDENT * " " + str(entry))
Expand Down
27 changes: 21 additions & 6 deletions financeager/listing.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,21 +24,36 @@ def from_elements(cls, elements, default_category=None, name=None):
def prettify(self,
*,
category_sort=DEFAULT_CATEGORY_ENTRY_SORT_KEY,
only_categories=False,
**entry_options):
"""Format listing (incl. name and header).
Category entries are sorted acc. to the given 'category_sort'.
'entry_options' are passed to CategoryEntry.string().
The header lists the relevant item types of BaseEntry, or, if
'only_categories' is set, an indicator for a column that displays the
share in the listing total of each category.
:return: str
"""
result = ["{1:^{0}}".format(CategoryEntry.TOTAL_LENGTH, self.name)]

header_line = "{3:{0}} {4:{1}} {5:{2}}".format(
CategoryEntry.NAME_LENGTH, BaseEntry.VALUE_LENGTH,
BaseEntry.DATE_LENGTH,
*[k.capitalize() for k in BaseEntry.ITEM_TYPES])
if BaseEntry.SHOW_EID:
header_line += " " + "ID".ljust(BaseEntry.EID_LENGTH)
if only_categories:
entry_options["total_listing_value"] = self.total_value()

header_line = "{3:{0}} {4:{1}} {5:>{2}} {6}".format(
CategoryEntry.NAME_LENGTH, BaseEntry.VALUE_LENGTH,
BaseEntry.DATE_LENGTH,
*[k.capitalize() for k in BaseEntry.ITEM_TYPES[:2]], "%",
BaseEntry.EID_LENGTH * " ")

else:
header_line = "{3:{0}} {4:{1}} {5:{2}}".format(
CategoryEntry.NAME_LENGTH, BaseEntry.VALUE_LENGTH,
BaseEntry.DATE_LENGTH,
*[k.capitalize() for k in BaseEntry.ITEM_TYPES])
if BaseEntry.SHOW_EID:
header_line += " " + "ID".ljust(BaseEntry.EID_LENGTH)

result.append(header_line)

sort_key = lambda e: getattr(e, category_sort)
Expand Down
3 changes: 3 additions & 0 deletions test/test_entries.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,9 @@ def test_str(self):
self.entry.string(),
"This Is Quite A Lo " + " 100.00" + 6 * " " + 4 * " " + "\n" +
" Entry " + " 100.00" + " 08-13 " + " 0")
self.assertEqual(
self.entry.string(total_listing_value=200),
"This Is Quite A Lo " + " 100.00" + " 50.0" + 4 * " ")


class SortBaseEntriesTestCase(unittest.TestCase):
Expand Down
13 changes: 13 additions & 0 deletions test/test_listing.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,19 @@ def test_prettify(self):
# Assert that original data was not modified
self.assertDictEqual(elements, elements_copy)

self.assertEqual(
prettify(
elements_copy,
default_category=CategoryEntry.DEFAULT_NAME,
only_categories=True),
" Earnings | Expenses \n" # noqa
"Name Value % | Name Value % \n" # noqa
"Unspecified 299.99 6.5 | Groceries 100.01 100.0 \n" # noqa
"Bank 4321.00 93.5 | \n" # noqa
"=============================================================================\n" # noqa
"Total 4620.99 | Total 100.01 \n" # noqa
"Difference 4520.98 ")

def test_prettify_stacked_layout(self):
elements = {
DEFAULT_TABLE: {
Expand Down

0 comments on commit ac5874a

Please sign in to comment.