|
55 | 55 | TYPE_CATEGORIES = { |
56 | 56 | 'amount': [amount.Amount], |
57 | 57 | 'account': [], # Must be manually assigned as account names are strings |
58 | | - 'position': [position.Position], |
59 | | - 'inventory': [inventory.Inventory], |
| 58 | + 'position': [position.Position, inventory.Inventory], |
60 | 59 | 'date': [datetime.date], |
61 | 60 | 'atomic': [] # fallback, default category |
62 | 61 | } |
63 | 62 |
|
| 63 | +# Category information for help display. Tuples: (title, description) |
| 64 | +CATEGORY_INFO = { |
| 65 | + 'amount': ("Amount and Commodity Functions", "An amount is a value with a currency/commodity."), |
| 66 | + 'account': ("Account Functions", ""), |
| 67 | + 'position': ("Position & Inventory Functions", "A position is a single amount held at cost.\n\nExample: 10 HOOL {100.30 USD}\n\nA collection of multiple positions is an inventory"), |
| 68 | + 'date': ("Date Functions", ""), |
| 69 | + 'atomic': ("Atomic Functions", "Work on basic types: strings, numbers, etc.") |
| 70 | +} |
| 71 | + |
64 | 72 |
|
65 | 73 | class style: |
66 | 74 | ERROR = '\033[31;1m' |
@@ -296,7 +304,27 @@ def completenames(self, text, *ignored): |
296 | 304 |
|
297 | 305 | def do_help(self, arg): |
298 | 306 | """List available commands with "help" or detailed help with "help cmd".""" |
299 | | - super().do_help(arg.lower()) |
| 307 | + if not arg: |
| 308 | + super().do_help(arg) |
| 309 | + return |
| 310 | + |
| 311 | + # Split arg by space to get command and additional arguments |
| 312 | + # e.g. "help functions amount" -> command="functions", args=["amount"] |
| 313 | + parts = arg.split() |
| 314 | + command = parts[0].lower() |
| 315 | + args = parts[1:] if len(parts) > 1 else [] |
| 316 | + |
| 317 | + # Check if there's a help method for this command |
| 318 | + help_method = getattr(self, f'help_{command}', None) |
| 319 | + if help_method and args: |
| 320 | + # Call the help method with the additional arguments |
| 321 | + try: |
| 322 | + help_method(' '.join(args)) |
| 323 | + except TypeError: |
| 324 | + # Fallback if the help method doesn't accept arguments |
| 325 | + super().do_help(command) |
| 326 | + else: |
| 327 | + super().do_help(command) |
300 | 328 |
|
301 | 329 | def do_history(self, arg): |
302 | 330 | """Print the command-line history.""" |
@@ -575,9 +603,9 @@ def on_Select(self, statement): |
575 | 603 | directive are made available in this context as well. Simple functions |
576 | 604 | (that return a single value per row) and aggregation functions (that |
577 | 605 | return a single value per group) are available. For the complete |
578 | | - list of supported columns and functions, see help on "targets". |
579 | | - You can also provide a wildcard here, which will select a reasonable |
580 | | - default set of columns for rendering a journal. |
| 606 | + list of supported columns and functions, see help on "targets" and |
| 607 | + "functions". You can also provide a wildcard here, which will |
| 608 | + select a reasonable default set of columns for rendering a journal. |
581 | 609 |
|
582 | 610 | from_expr: A logical expression that matches on the attributes of |
583 | 611 | the directives (not postings). This allows you to select a subset of |
@@ -654,10 +682,13 @@ def help_targets(self): |
654 | 682 |
|
655 | 683 | {columns} |
656 | 684 |
|
| 685 | + ---------------------------------------------------------------------- |
| 686 | +
|
657 | 687 | For available functions and aggregates, see "help functions". |
658 | 688 |
|
659 | 689 | """) |
660 | | - print(template.format(**_describe_columns(self.context.tables['postings'].columns)), |
| 690 | + |
| 691 | + print(template.format(columns = _describe_columns(self.context.tables['postings'].columns)), |
661 | 692 | file=self.outfile) |
662 | 693 |
|
663 | 694 | def help_from(self): |
@@ -694,51 +725,78 @@ def help_where(self): |
694 | 725 | print(template.format(columns=_describe_columns(self.context.tables['postings'].columns)), |
695 | 726 | file=self.outfile) |
696 | 727 |
|
697 | | - def help_functions(self): |
698 | | - """Show all available functions and aggregates grouped by type.""" |
699 | | - template = textwrap.dedent(""" |
700 | | -
|
701 | | - Functions are organized by the type of their first argument: |
702 | | -
|
703 | | - Amount Functions (work on amounts) |
704 | | - -------------------------------- |
705 | | -
|
706 | | - {amount_functions} |
707 | | -
|
708 | | - Position Functions (work on positions) |
709 | | - ------------------------------------ |
710 | | -
|
711 | | - {position_functions} |
712 | | -
|
713 | | - Inventory Functions (work on inventories) |
714 | | - --------------------------------------- |
715 | | -
|
716 | | - {inventory_functions} |
717 | | -
|
718 | | - Date Functions (work on dates) |
719 | | - ---------------------------- |
720 | | -
|
721 | | - {date_functions} |
722 | | -
|
723 | | - Atomic Functions (work on basic types: strings, numbers, etc.) |
724 | | - ----------------------------------------------------------- |
725 | | -
|
726 | | - {atomic_functions} |
727 | | -
|
728 | | - Aggregates |
729 | | - ---------- |
730 | | -
|
731 | | - {aggregates} |
732 | | -
|
733 | | - """) |
734 | | - print(template.format( |
735 | | - amount_functions=_describe_functions(query_compile.FUNCTIONS, aggregates=False, type_filter='amount'), |
736 | | - position_functions=_describe_functions(query_compile.FUNCTIONS, aggregates=False, type_filter='position'), |
737 | | - inventory_functions=_describe_functions(query_compile.FUNCTIONS, aggregates=False, type_filter='inventory'), |
738 | | - date_functions=_describe_functions(query_compile.FUNCTIONS, aggregates=False, type_filter='date'), |
739 | | - atomic_functions=_describe_functions(query_compile.FUNCTIONS, aggregates=False, type_filter='atomic'), |
740 | | - aggregates=_describe_functions(query_compile.FUNCTIONS, aggregates=True) |
741 | | - ), file=self.outfile) |
| 728 | + def help_functions(self, arg=None): |
| 729 | + """Show all available functions and aggregates grouped by type. |
| 730 | + |
| 731 | + Usage: help functions [type] |
| 732 | + |
| 733 | + Without type argument, shows only category descriptions. |
| 734 | + With type argument, shows functions for that specific type. |
| 735 | + Available types: amount, position, date, atomic, aggregates |
| 736 | + """ |
| 737 | + if arg is None: |
| 738 | + arg = '' |
| 739 | + else: |
| 740 | + arg = arg.strip().lower() |
| 741 | + |
| 742 | + |
| 743 | + if not arg: |
| 744 | + # Show only category descriptions when no type specified |
| 745 | + sections = [] |
| 746 | + sections.append("\nUsage: help functions [type]\n") |
| 747 | + sections.append("Functions are organized by the type of their main argument:\n") |
| 748 | + |
| 749 | + for category in TYPE_CATEGORIES.keys(): |
| 750 | + title, description = CATEGORY_INFO.get(category, (f"{category.title()} Functions", "")) |
| 751 | + section = f" {category}: {title}" |
| 752 | + if description: |
| 753 | + section += f" - {description}" |
| 754 | + sections.append(section) |
| 755 | + |
| 756 | + # Add aggregates info |
| 757 | + sections.append(" aggregates: Aggregation Functions - Functions that compute summary values across groups, as defined by the SELECT ... GROUP BY clause.") |
| 758 | + |
| 759 | + # Format output with proper wrapping and indentation |
| 760 | + wrapper = textwrap.TextWrapper(width=80, subsequent_indent=' ') |
| 761 | + formatted_sections = [] |
| 762 | + for section in sections: |
| 763 | + if section.startswith(' '): |
| 764 | + # For category lines, wrap with proper indentation |
| 765 | + wrapped = wrapper.fill(section) |
| 766 | + formatted_sections.append(wrapped) |
| 767 | + else: |
| 768 | + # For headers and usage, keep as is |
| 769 | + formatted_sections.append(section) |
| 770 | + print('\n'.join(formatted_sections), file=self.outfile) |
| 771 | + return |
| 772 | + |
| 773 | + # Show functions for specific type |
| 774 | + if arg == 'aggregates': |
| 775 | + aggregates_content = _describe_functions(query_compile.FUNCTIONS, aggregates=True) |
| 776 | + if aggregates_content.strip(): |
| 777 | + print("Aggregates\n----------\n", file=self.outfile) |
| 778 | + print(aggregates_content, file=self.outfile) |
| 779 | + else: |
| 780 | + print("No aggregate functions found.", file=self.outfile) |
| 781 | + return |
| 782 | + |
| 783 | + if arg not in TYPE_CATEGORIES: |
| 784 | + available_types = list(TYPE_CATEGORIES.keys()) + ['aggregates'] |
| 785 | + available_types = [t for t in available_types if t != 'account'] # Skip account |
| 786 | + print(f"Unknown type '{arg}'. Available types: {', '.join(available_types)}", file=self.outfile) |
| 787 | + return |
| 788 | + |
| 789 | + title, description = CATEGORY_INFO.get(arg, (f"{arg.title()} Functions", "")) |
| 790 | + underline = '-' * len(title) |
| 791 | + functions_content = _describe_functions(query_compile.FUNCTIONS, aggregates=False, type_filter=arg) |
| 792 | + |
| 793 | + if functions_content.strip(): |
| 794 | + print(f"{title}\n{underline}", file=self.outfile) |
| 795 | + if description: |
| 796 | + print(f"\n{description}", file=self.outfile) |
| 797 | + print(f"\n{functions_content}", file=self.outfile) |
| 798 | + else: |
| 799 | + print(f"No functions found for type '{arg}'.", file=self.outfile) |
742 | 800 |
|
743 | 801 |
|
744 | 802 | def _describe_columns(columns): |
|
0 commit comments