Metadata-Version: 2.4
Name: blursedmenu
Version: 0.1.0
Summary: A framework to build menu-driven text user interfaces in any terminal
Author-email: vonshednob <contact+blursedmenu@vonshednob.cc>
License-Expression: EUPL-1.2
Project-URL: Homepage, https://vonshednob.cc/blursedmenu
Project-URL: Repository, https://codeberg.org/vonshednob/blursedmenu
Project-URL: Issues, https://codeberg.org/vonshednob/blursedmenu/issues
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Console
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Software Development :: Libraries
Classifier: Topic :: Terminals
Requires-Python: >=3.12
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

# Blursed Menu

`blursedmenu` is a framework to build menu-driven text user interfaces in
any terminal.

This is at the same time cursed, in that it is a weird way to structure a
program, and blessed, because it does not use the `(n)curses` library.


## How To Use It In Your Project

The most common way to use `blursedmenu` would be to add it as a dependency
to your project.

Alternatively you might just copy the `blursedmenu.py` file into your
project. Note that the copyright notice and license information at the top
of the file still apply.


## Example

A minimal example of a menu usage can be found in `minimal.py` (although
it's not that minimal because it implements a `item_action`). If you want
the minimal thing, but don't like classes, have a look at
`minimal_no_class.py` - it does the same thing as `minimal.py`.

There is also a more complex example of a file browser written using `Menu` in
`demo.py`.


## Features

 - no (n)curses, any terminal can show this
 - optional readline support (for completion and history)
 - for lazy typers: commands can be abbreviated to their shortest
   non-ambiguous name (e.g. `h` instead of `help`)


## Structure and Usage

In general a `Menu` has `Item`s and `Command`s.

`Item`s are stored in `Menu.items` by calling `Menu.build_items` and can
be listed in an enumerated form, so the user can easily refer to the items
by number.

`Command`s are used to interact with the items and to control the program
itself. By default these commands are set up:

 - `quit`, implemented in `QuitCommand` to exit the menu,
 - `list`, implemented in `ListCommand` to list the items,
 - `help`, implemented in `HelpComand` to show the help of other commands.

A menu is run by calling `Menu.run()` and it can return an exit code to
indicate success (it returns `0` by default).

To implement your own menu, you usually want to add an implementation to
`Menu.build_items` and add your own commands to your menu's
`self.commands`.


### Items

Items are rebuilt every time with a call to `build_items()` just before
the user is being asked to provide input (see _Best Practices_ below).

Individual menu items can be as simple as strings (see `minimal.py`) or
your own implementation of `Item`, in case you have more complex data
structures than a string.  
A good example is `SelectableItem`, which allows the user to toggle the
`selected` state of an item.


### Commands

Your custom commands should implement `self.execute()` and use
`self.item()` to resolve the user-provided item number and do stuff with
it.
The documentation string of your custom command is shown to the user by the
`HelpCommand`.


### Item Action

If you only want the user to select an item and go on with it, you should
use assign a function to `Menu.item_action`. That function will receive the
selected menu item as the first argument.

Note that `SelectableItem`s will never use the `item_action` (because these
toggle their 'selected' state instead), so you want to have the item action
also available as a command.


### Various Other Parts

The prompt that is shown to the user is controlled via the `Menu.prompt`
variable. You could set the prompt based on the current context, menu, or
state.

There is a `Menu.description` that will be shown to the user when the menu
is started. You can use it to provide some information what the
user can do.


### User Input

If you want to get input from the user, you could just use `input`, but
there's also `Menu.ask` that handles errors and, if `readline` support is
available, allows you to provide the default value and even tab-completion.


### Sub-menus

To launch a submenu, create a command and in the `execute()` function
instantiate the new menu and run it.

It's usually a good idea for submenus to pass the originating menu as
`parent` into the constructor of `Menu`.

Have a look at `submenu.py` for an example.


### Hooks

There are a few hooks that you can use:

 - `on_start` is executed immediately when the menu's `run` is called,
 - `before_input` is executed every time just before the user is asked for input,
 - `after_execute` is executed after a command has been run and the
   returned value from that command's `execute` call is passed in as the
   first (and only) argument.
 - `on_exit` is the last thing that's executed in the menu's `run` function
   and the returned value from `on_exit` is also returned from `run`.


### Best Practices

The `build_items()` function is called from the default implementation of
`Menu.before_input`. That means it is run every time before the user is
prompted for input!

You may want to consider to not rebuild the items with this:

```python
def build_items(self):
    if len(self.items) > 0:
        return
```

Now, if you want to rebuild the items (for example from a 'reload' command
or otherwise when you know the items have changed), you simply clear the
menu's items.

Here's an example command that does just that:

```python
class ReloadCommand(blursedmenu.Command):
    def execute(self, *_):
        self.menu.items = []
```


## Copyright and License

Copyright 2026 vonshednob, licensed under the [EUPL](https://spdx.org/licenses/EUPL-1.2)

