"""
Module: cli.menu_constructors.py
================================
This module is designed to create, manage, and navigate through a menu interface in a console environment.
This Python module is designed to create, manage, and navigate through a menu interface in a console environment.
The module contains functions for constructing and displaying various types of menus (directed and undirected),
handling user navigation, executing menu-associated functions, displaying structured text information, and presenting
analysis results in an organized, readable format.
Module-level variables:
-----------------------
menu_stack : list
A stack to keep track of the previous menus. This stack is used to enable backward and forward navigation in
undirected menus.
Functions
---------
- display_menu_print_results
- display_menu_print_textblock
- display_menu_request_selection
- display_menu_undirected
- print_menu_entries
- print_textblock
- util_convert_imat_datacont_to_pd_dataframe
- util_convert_pd_dataframe_to_imat_datacont
These functions help in providing a smooth user interface for the command-line operation, ensuring a consistent user
experience, and reducing complexity in navigating the tool's functionalities. By using these functions,
the Interactive Music Analysis Tool (I-MaT) system provides an interactive and user-friendly command-line interface
for music analysis.
"""
import os
import textwrap
import pandas as pd
from iMaT.src.constants import TITLE_TEXT
from iMaT.src.utils.error_handling import handle_error
menu_stack = [] # Stack to keep track of the previous menus
[docs]def print_textblock(menu_columns_description: list, menu_entries: list[list], textblock_sep_line=True) -> None:
"""
Prints a formatted text block in the console.
This function takes in a description for menu columns, a list of menu entries, and a boolean indicating whether
to separate text blocks with a line. It uses this information to print a formatted text block on the console.
The function is used as a helper within `display_menu_print_textblock` which not only prints the text but also
displays a full menu above the printed text. However, `print_textblock` can be independently used to print text
without displaying a full menu.
Parameters
----------
menu_columns_description : list
A list containing column descriptions. For instance, ["<Identifier>", "<Description>"].
menu_entries : list
A list of lists where each sublist contains the title and message for a text block.
textblock_sep_line : bool, optional
If True, a separator line is printed after each text block. By default, it is set to True.
Returns
-------
None
See Also
--------
display_menu_print_textblock : For displaying custom text blocks using the same structure as a menu.
Examples
--------
Here is how to use `print_textblock`:
>>> menu_columns_description = ["", "Message"]
>>> menu_entries = [
... ["Message 1", "Textblock 1"],
... ["Message 2", "Textblock 2"],
... ["Message 3", "Textblock 3"],
... ]
>>> print_textblock(menu_columns_description, menu_entries)
"""
try:
first_col_width = max(max(len(entry[0]) for entry in menu_entries), len(menu_columns_description[0])) + 5
# "+5": Or however wide you want the title column to be
message_col_width = 80 # Or however wide you want the message column to be
# Print each column name
print(f"{menu_columns_description[0]}{'':{first_col_width-len(menu_columns_description[0])}} {menu_columns_description[1]}")
print("")
# Now print each menu item, wrapped if it exceeds 80 characters.
for row in menu_entries:
title, message = row
wrapped_message = textwrap.fill(message, message_col_width)
for i, second_col_line in enumerate(wrapped_message.split('\n')):
if i == 0:
print(f"{title:{first_col_width}} {second_col_line}")
else:
print(f"{'':{first_col_width}} {second_col_line}")
if textblock_sep_line:
print("") # Empty line after each message
if not textblock_sep_line:
print("")
except Exception as e:
handle_error(e)
[docs]def util_convert_pd_dataframe_to_imat_datacont(pd_dataframe: pd.DataFrame,
menu_title: str = "Analysis Results",
menu_guideline: str = "Please see the following analysis results:",
menu_requested_input: str = "<To continue, please press Enter>",
menu_columns_description: list[str] = None) -> dict:
"""
Converts a pandas DataFrame to an I-MaT data container format.
This function converts a pandas DataFrame into a data format compatible with I-MaT's display functions. It's
particularly useful for preparing data to be displayed in the console via `display_menu_print_results()`.
Parameters
----------
pd_dataframe : pd.DataFrame
DataFrame containing the data to be displayed.
menu_title : str, optional
Title for the data display. Defaults to "Analysis Results".
menu_guideline : str, optional
Instruction for the user. Defaults to "Please see the following analysis results:".
menu_requested_input : str, optional
Prompt for user input. Defaults to "<To continue, please press Enter>".
menu_columns_description : List[str], optional
Descriptions for data columns. If not provided, the DataFrame's column names will be used.
Returns
-------
dict
A dictionary in I-MaT data container format that can be used by the `display_menu_print_results()` function.
See Also
--------
display_menu_print_results: Use this function to display the converted DataFrame in console.
util_convert_imat_datacont_to_pd_dataframe: For the reverse operation - converting an I-MaT data container
into a pandas DataFrame before processing.
Examples
--------
>>> df = pd.DataFrame({
... 'col1': ['Result 1', 'Result 2', 'Result 3'],
... 'col2': ['<Displays the 1st Column>', '<Displays the 1st Column>', '<Displays the 1st Column>'],
... 'col3': ['<Displays the 2nd Column>', '<Displays the 2nd Column>', '<Displays the 2nd Column>']
... })
>>> results_dict = util_convert_pd_dataframe_to_imat_datacont(df)
>>> display_menu_print_results(results_dict)
This will display a result set with 3 rows in the console. The actual output is determined by the dictionary
returned from `util_convert_pd_dataframe_to_imat_datacont()`.
An I-MaT data container created from a DataFrame would look like this:
>>> def example_analysis_results_dict():
... return {
... "menu_displayed_text": [
... "Analysis Results",
... "Please see the following analysis results:",
... "<To continue, please press Enter>",
... ["<Identifier>", "<Description>", "<Description>"],
... ],
... "menu_entries_results": [
... ["Result 1", "<Displays the 1st Column>", "<Displays the 2nd Column>"],
... ["Result 2", "<Displays the 1st Column>", "<Displays the 2nd Column>"],
... ["Result 3", "<Displays the 1st Column>", "<Displays the 2nd Column>"],
... ]
... }
"""
try:
if menu_columns_description is None:
# if no menu_columns_description is provided, use the DataFrame's column names'
menu_columns_description = list(pd_dataframe.columns)
imat_datacont = {
"menu_displayed_text": [
menu_title,
menu_guideline,
menu_requested_input,
menu_columns_description,
],
"menu_entries_results": []
}
for _, row in pd_dataframe.iterrows():
imat_datacont["menu_entries_results"].append(list(row))
return imat_datacont
except Exception as e:
handle_error(e)
[docs]def util_convert_imat_datacont_to_pd_dataframe(imat_cont: dict) -> pd.DataFrame:
"""
Converts an I-MaT data container to a pandas DataFrame format.
This function takes an I-MaT data container and converts it into a pandas DataFrame. It's an excellent way to
facilitate user-friendly, manual data entry, as the vertical orientation of the I-MaT data container is easier to
read than a typical pandas DataFrame.
Parameters
----------
imat_cont : dict
I-MaT data container dictionary containing the data to be converted into a DataFrame.
Returns
-------
pd.DataFrame
A DataFrame containing the data from the I-MaT data container.
See Also
--------
display_menu_print_results: Use this function to display data from the I-MaT data container.
util_convert_pd_dataframe_to_imat_datacont: For the reverse operation - converting a pandas DataFrame
into an I-MaT data container.
Examples
--------
>>> df = pd.DataFrame({
... 'col1': ['Result 1', 'Result 2', 'Result 3'],
... 'col2': ['<Displays the 1st Column>', '<Displays the 1st Column>', '<Displays the 1st Column>'],
... 'col3': ['<Displays the 2nd Column>', '<Displays the 2nd Column>', '<Displays the 2nd Column>']
... })
>>> imat_cont = {
... "menu_displayed_text": [
... "Analysis Results",
... "Please see the following analysis results:",
... "<To continue, please press Enter>",
... ["<Identifier>", "<Description>", "<Description>"],
... ],
... "menu_entries_results": [
... ["Result 1", "<Displays the 1st Column>", "<Displays the 2nd Column>"],
... ["Result 2", "<Displays the 1st Column>", "<Displays the 2nd Column>"],
... ["Result 3", "<Displays the 1st Column>", "<Displays the 2nd Column>"],
... ]
... }
>>> df = util_convert_imat_datacont_to_pd_dataframe(imat_cont)
This will convert the I-MaT data container to a DataFrame with 3 rows. The DataFrame can then be used for further
data processing or analysis.
"""
try:
column_names = imat_cont['menu_displayed_text'][2]
data = imat_cont['menu_entries_results']
pd_dataframe = pd.DataFrame(data, columns=column_names)
return pd_dataframe
except Exception as e:
handle_error(e)