aegirhall / console-menu

A simple Python menu system for building terminal user interfaces.
MIT License
365 stars 58 forks source link

Load Menu Definition from a YAML formatted String #74

Open brucegibbins opened 2 years ago

brucegibbins commented 2 years ago

Hi.

I'd like to suggest an enhancement that I have already coded a Proof Of Concept for but I am not sure what your feelings would be on the concept.

The idea is to basically use a YAML formatted string which could possibly be derived from a configuration file. Something like the following sample which could be extended to include extra meu attributes.

menudef = """\
    root:
        title: Root Menu
        subtitle: This is the Root Menu Subtitle
        items:
            - text: Menu Item 1
              class: MenuItem

            - text: Call Class Function
              class: ClassFunctionItem
              action: consolemenu.screen.Screen.input 
              args: ["Please enter input here: "]

            - text: Call Module Function
              class: ModuleFunctionItem
              action: examples.example7.helloworld 
              args: ["Argument 1"]

            - text: Command
              class: CommandItem
              action: 'cmd /c \"echo this is a shell. Press enter to continue." && set /p=\"'   
"""

Then it could be called as

 menu = ConsoleMenu("Root Menu", "This is the Root Menu Subtitle", menudef=yaml.safe_load(menudef))
 menu.show()

Which results in

 ┌─────────────────────────────────────────────────────────────────────────┐
 │                                                                         │
 │  Root Menu                                                              │
 │                                                                         │
 │  This is the Root Menu Subtitle                                         │
 │                                                                         │
 │                                                                         │
 │    1 - Menu Item 1                                                      │
 │    2 - Call Class Function                                              │
 │    3 - Call Module Function                                             │
 │    4 - Command                                                          │
 │    5 - Exit                                                             │
 │                                                                         │
 │                                                                         │
 └─────────────────────────────────────────────────────────────────────────┘

In the way of explanation, the "class" element is being used in the existing POC to establish what type of menu item the content is meant to instantiate when building the ConsoleMenu instance. But I'm guessing there may be a more elegant way of doing that and removing the need for the element in the definition.

My thought was to include a new class Function for the ConsoleMenu class that took a Dict as an argument to make it a bit more usable in the future. Then the calling code could read, validate and possibly preparse the yaml content creating a dict that could then be consumed by the new function.

This would require the ConsoleMenu instance to exist and so the question would be whether this new function overload any existing attributes (specifically for the root menu) or simply supplemented any missing ones.

Obviously, the current methods provide the most flexibility and options but this approach would allow for the externalisation of the menu definition and over time extended to incorporate more options. In my use case, I can add and remove menu items without touching any code and thus change deployment by simply adjusting a configuration file.

The other consideration would be the security aspects of having an externalised definition that could allow someone to insert malicious "command" menu items. The obvious workaround, would be not to include them in the loader. But I have just included it for completeness.

I am happy to raise a PR for this if you think it would add any value to the library.

Cheers

aegirhall commented 2 years ago

HI @brucegibbins, thanks, this looks like an outstanding feature! I'm sure others would find it useful as well. Please go ahead and raise a PR so I can take a look. One question: how does the yaml handle submenus?

brucegibbins commented 2 years ago

Hi @aegirhall sorry for the delay - just been a bit "time poor". I guess that was my comment :-)

Obviously, the current methods provide the most flexible options

My initial POC just handled a single level but in my mind it was a case of nested YAML entries with some arbitrary limit (I guess). To be honest, I hadn't gone that deep into that aspect. Before, I do the PR, I'd like to tidy up what I have and adjust a few things that should make it blend a bit better with the existing framework and then maybe raise a PR at that point. It would be my first so I'd need some guidance on the changes I am suggesting so that they blend in for the wider community.