menataur 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- menataur-0.1.0/LICENSE +41 -0
- menataur-0.1.0/PKG-INFO +8 -0
- menataur-0.1.0/README.md +242 -0
- menataur-0.1.0/menataur.egg-info/PKG-INFO +8 -0
- menataur-0.1.0/menataur.egg-info/SOURCES.txt +9 -0
- menataur-0.1.0/menataur.egg-info/dependency_links.txt +1 -0
- menataur-0.1.0/menataur.egg-info/requires.txt +1 -0
- menataur-0.1.0/menataur.egg-info/top_level.txt +1 -0
- menataur-0.1.0/menataur.py +683 -0
- menataur-0.1.0/pyproject.toml +9 -0
- menataur-0.1.0/setup.cfg +4 -0
menataur-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
Menataur (main)
|
|
2
|
+
---------------------------------------------------------------------------------------------
|
|
3
|
+
MIT License
|
|
4
|
+
|
|
5
|
+
Copyright (c) 2026 Gratonic (https://github.com/Gratonic/Minotaur) and FailurePoint (https://github.com/FailurePoint)
|
|
6
|
+
|
|
7
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
8
|
+
|
|
9
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
10
|
+
|
|
11
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
12
|
+
|
|
13
|
+
Colorama
|
|
14
|
+
---------------------------------------------------------------------------------------------
|
|
15
|
+
Copyright (c) 2010 Jonathan Hartley
|
|
16
|
+
All rights reserved.
|
|
17
|
+
|
|
18
|
+
Redistribution and use in source and binary forms, with or without
|
|
19
|
+
modification, are permitted provided that the following conditions are met:
|
|
20
|
+
|
|
21
|
+
* Redistributions of source code must retain the above copyright notice, this
|
|
22
|
+
list of conditions and the following disclaimer.
|
|
23
|
+
|
|
24
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
|
25
|
+
this list of conditions and the following disclaimer in the documentation
|
|
26
|
+
and/or other materials provided with the distribution.
|
|
27
|
+
|
|
28
|
+
* Neither the name of the copyright holder nor the names of its contributors
|
|
29
|
+
may be used to endorse or promote products derived from this software without
|
|
30
|
+
specific prior written permission.
|
|
31
|
+
|
|
32
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
33
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
34
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
35
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
36
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
37
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
38
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
39
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
40
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
41
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
menataur-0.1.0/PKG-INFO
ADDED
menataur-0.1.0/README.md
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
# What is Menataur?
|
|
2
|
+
|
|
3
|
+
Menataur is a menu interface builder module for Python3.10.x. It is super easy to use and makes
|
|
4
|
+
constructing menu interfaces a breeze, turning hours, days, or even months of work into just a few minutes.
|
|
5
|
+
|
|
6
|
+
# How do you construct an awesome menu interface with Menataur? Here's a tested example...
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
pip3 install menataur
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
```python
|
|
13
|
+
import menataur
|
|
14
|
+
|
|
15
|
+
# [=== Test Menu 1 ===] #
|
|
16
|
+
|
|
17
|
+
# create Banner
|
|
18
|
+
banner_1 = menataur.Banner()
|
|
19
|
+
|
|
20
|
+
banner_1.title = r"""
|
|
21
|
+
_____ _ _
|
|
22
|
+
|_ _| | | | |
|
|
23
|
+
| | _ __ ___| |_ _ __ _ _ _ __ ___ ___ _ __ | |_ ___
|
|
24
|
+
| || '_ \/ __| __| '__| | | | '_ ` _ \ / _ \ '_ \| __/ __|
|
|
25
|
+
_| || | | \__ \ |_| | | |_| | | | | | | __/ | | | |_\__ \
|
|
26
|
+
\___/_| |_|___/\__|_| \__,_|_| |_| |_|\___|_| |_|\__|___/"""
|
|
27
|
+
banner_1.title_colors = ["cyan", "yellow", "green", "magenta"]
|
|
28
|
+
banner_1.title_bar = "_______________________________________________________________/"
|
|
29
|
+
banner_1.title_bar_color = "blue"
|
|
30
|
+
banner_1.program_name = "Menataur"
|
|
31
|
+
banner_1.program_name_color = "blue"
|
|
32
|
+
banner_1.program_version_number = 1.0
|
|
33
|
+
banner_1.program_version_number_color = "yellow"
|
|
34
|
+
banner_1.operating_system_support_message = "This Program Supports Linux, MacOS, and Windows"
|
|
35
|
+
banner_1.operating_system_support_message_color = "light_green"
|
|
36
|
+
|
|
37
|
+
# create Options
|
|
38
|
+
options_1 = menataur.Options()
|
|
39
|
+
|
|
40
|
+
options_1.option_description_color = "blue"
|
|
41
|
+
options_1.option_number_color = "yellow"
|
|
42
|
+
options_1.option_name_color = "magenta"
|
|
43
|
+
options_1.navigation_option_color = "gray"
|
|
44
|
+
|
|
45
|
+
options_1.add_option("A string instrument is a musical instrument that produces sound through the vibration of strings", 1, "Stringed")
|
|
46
|
+
options_1.add_option("A keyboard instrument is a musical instrument played using a keyboard, a row of levers pressed by the fingers", 2, "Keyboards")
|
|
47
|
+
options_1.add_option("A percussion instrument is a musical instrument that makes sound by being struck or scraped by a beater", 3, "Percussion")
|
|
48
|
+
|
|
49
|
+
# create Prompt
|
|
50
|
+
prompt_1 = menataur.Prompt()
|
|
51
|
+
|
|
52
|
+
prompt_1.prompt_message_color = "light_magenta"
|
|
53
|
+
prompt_1.prompt_message = "Select An Option From The Menu [Ex: 3]: "
|
|
54
|
+
|
|
55
|
+
# create Menu
|
|
56
|
+
menu_1 = menataur.Menu()
|
|
57
|
+
|
|
58
|
+
menu_1.name = "main_menu"
|
|
59
|
+
menu_1.main_menu = True
|
|
60
|
+
|
|
61
|
+
menu_1.add_banner(banner_1)
|
|
62
|
+
menu_1.add_options(options_1)
|
|
63
|
+
menu_1.add_prompt(prompt_1)
|
|
64
|
+
|
|
65
|
+
# [=== Test Menu 2 ===] #
|
|
66
|
+
|
|
67
|
+
# create Banner
|
|
68
|
+
banner_2 = menataur.Banner()
|
|
69
|
+
|
|
70
|
+
banner_2.title = r"""
|
|
71
|
+
_____ _ _ _
|
|
72
|
+
/ ___| | (_) | |
|
|
73
|
+
\ `--.| |_ _ __ _ _ __ __ _ ___ __| |
|
|
74
|
+
`--. \ __| '__| | '_ \ / _` |/ _ \/ _` |
|
|
75
|
+
/\__/ / |_| | | | | | | (_| | __/ (_| |
|
|
76
|
+
\____/ \__|_| |_|_| |_|\__, |\___|\__,_|
|
|
77
|
+
__/ |
|
|
78
|
+
|___/ """
|
|
79
|
+
banner_2.title_colors = ["cyan", "yellow", "green", "magenta"]
|
|
80
|
+
banner_2.title_bar = "_________________________________________________/"
|
|
81
|
+
banner_2.title_bar_color = "blue"
|
|
82
|
+
banner_2.program_name = "Stringed"
|
|
83
|
+
banner_2.program_name_color = "blue"
|
|
84
|
+
banner_2.program_version_number = 1.0
|
|
85
|
+
banner_2.program_version_number_color = "yellow"
|
|
86
|
+
banner_2.operating_system_support_message = "This Program Supports Linux, MacOS, and Windows"
|
|
87
|
+
banner_2.operating_system_support_message_color = "light_green"
|
|
88
|
+
|
|
89
|
+
# create Options
|
|
90
|
+
options_2 = menataur.Options()
|
|
91
|
+
|
|
92
|
+
options_2.option_description_color = "blue"
|
|
93
|
+
options_2.option_number_color = "yellow"
|
|
94
|
+
options_2.option_name_color = "magenta"
|
|
95
|
+
options_2.navigation_option_color = "gray"
|
|
96
|
+
|
|
97
|
+
options_2.add_option("An string instrument played by strumming or plucking its strings", 1, "Guitar")
|
|
98
|
+
options_2.add_option("A small, high-pitched string instrument usually played with a bow", 2, "Violin")
|
|
99
|
+
options_2.add_option("A small, four-string instrument played by strumming or plucking", 3, "Ukulele")
|
|
100
|
+
|
|
101
|
+
# create Prompt
|
|
102
|
+
prompt_2 = menataur.Prompt()
|
|
103
|
+
|
|
104
|
+
prompt_2.prompt_message_color = "light_magenta"
|
|
105
|
+
prompt_2.prompt_message = "Select An Option From The Menu [Ex: 3]: "
|
|
106
|
+
|
|
107
|
+
# create Menu
|
|
108
|
+
menu_2 = menataur.Menu()
|
|
109
|
+
|
|
110
|
+
menu_2.name = "stringed_menu"
|
|
111
|
+
|
|
112
|
+
menu_2.add_banner(banner_2)
|
|
113
|
+
menu_2.add_options(options_2)
|
|
114
|
+
menu_2.add_prompt(prompt_2)
|
|
115
|
+
|
|
116
|
+
menu_2.final_menu = True
|
|
117
|
+
|
|
118
|
+
# [=== Test Menu 3 ===] #
|
|
119
|
+
|
|
120
|
+
# create Banner
|
|
121
|
+
banner_3 = menataur.Banner()
|
|
122
|
+
|
|
123
|
+
banner_3.title = r"""
|
|
124
|
+
_ __ _ _
|
|
125
|
+
| | / / | | | |
|
|
126
|
+
| |/ / ___ _ _| |__ ___ __ _ _ __ __| |___
|
|
127
|
+
| \ / _ \ | | | '_ \ / _ \ / _` | '__/ _` / __|
|
|
128
|
+
| |\ \ __/ |_| | |_) | (_) | (_| | | | (_| \__ \
|
|
129
|
+
\_| \_/\___|\__, |_.__/ \___/ \__,_|_| \__,_|___/
|
|
130
|
+
__/ |
|
|
131
|
+
|___/ """
|
|
132
|
+
banner_3.title_colors = ["cyan", "yellow", "green", "magenta"]
|
|
133
|
+
banner_3.title_bar = "_________________________________________________/"
|
|
134
|
+
banner_3.title_bar_color = "blue"
|
|
135
|
+
banner_3.program_name = "Keyboards"
|
|
136
|
+
banner_3.program_name_color = "blue"
|
|
137
|
+
banner_3.program_version_number = 1.0
|
|
138
|
+
banner_3.program_version_number_color = "yellow"
|
|
139
|
+
banner_3.operating_system_support_message = "This Program Supports Linux, MacOS, and Windows"
|
|
140
|
+
banner_3.operating_system_support_message_color = "light_green"
|
|
141
|
+
|
|
142
|
+
# create Options
|
|
143
|
+
options_3 = menataur.Options()
|
|
144
|
+
|
|
145
|
+
options_3.option_description_color = "blue"
|
|
146
|
+
options_3.option_number_color = "yellow"
|
|
147
|
+
options_3.option_name_color = "magenta"
|
|
148
|
+
options_3.navigation_option_color = "gray"
|
|
149
|
+
|
|
150
|
+
options_3.add_option("A keyboard instrument that makes sound when hammers strike its strings", 1, "Piano")
|
|
151
|
+
options_3.add_option("A keyboard instrument that produces sound by sending air through pipes or electronic tone generators", 2, "Organ")
|
|
152
|
+
options_3.add_option("A keyboard instrument that produces a delicate, bell-like sound when hammers strike metal bars", 3, "Celeste")
|
|
153
|
+
|
|
154
|
+
# create Prompt
|
|
155
|
+
prompt_3 = menataur.Prompt()
|
|
156
|
+
|
|
157
|
+
prompt_3.prompt_message_color = "light_magenta"
|
|
158
|
+
prompt_3.prompt_message = "Select An Option From The Menu [Ex: 3]: "
|
|
159
|
+
|
|
160
|
+
# create Menu
|
|
161
|
+
menu_3 = menataur.Menu()
|
|
162
|
+
|
|
163
|
+
menu_3.name = "keyboards_menu"
|
|
164
|
+
|
|
165
|
+
menu_3.add_banner(banner_3)
|
|
166
|
+
menu_3.add_options(options_3)
|
|
167
|
+
menu_3.add_prompt(prompt_3)
|
|
168
|
+
|
|
169
|
+
menu_3.final_menu = True
|
|
170
|
+
|
|
171
|
+
# [=== Test Menu 4 ===] #
|
|
172
|
+
|
|
173
|
+
# create Banner
|
|
174
|
+
banner_4 = menataur.Banner()
|
|
175
|
+
|
|
176
|
+
banner_4.title = r"""
|
|
177
|
+
______ _
|
|
178
|
+
| ___ \ (_)
|
|
179
|
+
| |_/ /__ _ __ ___ _ _ ___ ___ _ ___ _ __
|
|
180
|
+
| __/ _ \ '__/ __| | | / __/ __| |/ _ \| '_ \
|
|
181
|
+
| | | __/ | | (__| |_| \__ \__ \ | (_) | | | |
|
|
182
|
+
\_| \___|_| \___|\__,_|___/___/_|\___/|_| |_|"""
|
|
183
|
+
banner_4.title_colors = ["cyan", "yellow", "green", "magenta"]
|
|
184
|
+
banner_4.title_bar = "_________________________________________________/"
|
|
185
|
+
banner_4.title_bar_color = "blue"
|
|
186
|
+
banner_4.program_name = "Percussion"
|
|
187
|
+
banner_4.program_name_color = "blue"
|
|
188
|
+
banner_4.program_version_number = 1.0
|
|
189
|
+
banner_4.program_version_number_color = "yellow"
|
|
190
|
+
banner_4.operating_system_support_message = "This Program Supports Linux, MacOS, and Windows"
|
|
191
|
+
banner_4.operating_system_support_message_color = "light_green"
|
|
192
|
+
|
|
193
|
+
# create Options
|
|
194
|
+
options_4 = menataur.Options()
|
|
195
|
+
|
|
196
|
+
options_4.option_description_color = "blue"
|
|
197
|
+
options_4.option_number_color = "yellow"
|
|
198
|
+
options_4.option_name_color = "magenta"
|
|
199
|
+
options_4.navigation_option_color = "gray"
|
|
200
|
+
|
|
201
|
+
options_4.add_option("A large drum that produces a deep, low sound when struck", 1, "Bass Drum")
|
|
202
|
+
options_4.add_option("A metal percussion instrument that makes a bright crash or ringing sound when struck", 2, "Cymbal")
|
|
203
|
+
options_4.add_option("Tall, single-headed hand drums played with the hands, often in pairs", 3, "Congas")
|
|
204
|
+
|
|
205
|
+
# create Prompt
|
|
206
|
+
prompt_4 = menataur.Prompt()
|
|
207
|
+
|
|
208
|
+
prompt_4.prompt_message_color = "light_magenta"
|
|
209
|
+
prompt_4.prompt_message = "Select An Option From The Menu [Ex: 3]: "
|
|
210
|
+
|
|
211
|
+
# create Menu
|
|
212
|
+
menu_4 = menataur.Menu()
|
|
213
|
+
|
|
214
|
+
menu_4.name = "percussion_menu"
|
|
215
|
+
|
|
216
|
+
menu_4.add_banner(banner_4)
|
|
217
|
+
menu_4.add_options(options_4)
|
|
218
|
+
menu_4.add_prompt(prompt_4)
|
|
219
|
+
|
|
220
|
+
menu_4.final_menu = True
|
|
221
|
+
|
|
222
|
+
# [=== Datamapping === ] #
|
|
223
|
+
|
|
224
|
+
main_menu_datamap = {
|
|
225
|
+
1: menu_2,
|
|
226
|
+
2: menu_3,
|
|
227
|
+
3: menu_4
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
menu_1.datamap = main_menu_datamap
|
|
231
|
+
|
|
232
|
+
# [=== Menataur (menu interface) ===] #
|
|
233
|
+
|
|
234
|
+
# create a Menataur (menu interface)
|
|
235
|
+
menu_interface = menataur.Menataur()
|
|
236
|
+
|
|
237
|
+
# specify the start Menu
|
|
238
|
+
menu_interface.start_menu = menu_1
|
|
239
|
+
|
|
240
|
+
# run the Menataur (menu interface)
|
|
241
|
+
user_choices = menu_interface.run()
|
|
242
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
colorama>=0.4.6
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
menataur
|
|
@@ -0,0 +1,683 @@
|
|
|
1
|
+
r"""
|
|
2
|
+
# :: Author Information and Program Details :: #
|
|
3
|
+
|
|
4
|
+
File Name: menataur.py
|
|
5
|
+
Author(s): Gratonic (https://github.com/Gratonic) and FailurePoint (https://github.com/FailurePoint)
|
|
6
|
+
Language: Python 3.13.5
|
|
7
|
+
Dependencie(s): colorama
|
|
8
|
+
Last Modified: Septemeber 26, 2026
|
|
9
|
+
|
|
10
|
+
# :: Description :: #
|
|
11
|
+
|
|
12
|
+
This program was designed to make writing command-line menu interfaces easier.
|
|
13
|
+
It splits each menu into blocks represented by classes. These block classes are combined to create
|
|
14
|
+
a Menu instance, which can then be combined with other Menu instances within the Menataur class.
|
|
15
|
+
The Menataur class organizes the menu components and uses a navigation algorithm to allow
|
|
16
|
+
users to move between them.
|
|
17
|
+
|
|
18
|
+
note:
|
|
19
|
+
When reading the menu navigation algorithm code in this program (split between Menu and Menataur), think about a Binary Tree
|
|
20
|
+
and it will begin to make a lot more sense. I believe this is the simplest way to make sb ense
|
|
21
|
+
of the navigation algorithm.
|
|
22
|
+
|
|
23
|
+
# :: Example Usage :: #
|
|
24
|
+
|
|
25
|
+
# create the banner
|
|
26
|
+
banner = Banner()
|
|
27
|
+
|
|
28
|
+
banner.title =
|
|
29
|
+
___ ___ _
|
|
30
|
+
| \/ | | |
|
|
31
|
+
| . . | ___ _ __ __ _| |_ __ _ _ _ _ __
|
|
32
|
+
| |\/| |/ _ \ '_ \ / _` | __/ _` | | | | '__|
|
|
33
|
+
| | | | __/ | | | (_| | || (_| | |_| | |
|
|
34
|
+
\_| |_/\___|_| |_|\__,_|\__\__,_|\__,_|_|
|
|
35
|
+
banner.title_colors = ["cyan", "yellow", "green", "magenta"]
|
|
36
|
+
banner.title_bar = "_________________________________________________/"
|
|
37
|
+
banner.title_bar_color = "blue"
|
|
38
|
+
banner.program_name = "Menataur"
|
|
39
|
+
banner.program_name_color = "blue"
|
|
40
|
+
banner.program_version_number = 1.0
|
|
41
|
+
banner.program_version_number_color = "yellow"
|
|
42
|
+
banner.operating_system_support_message = "This Program Supports Linux, MacOS, and Windows"
|
|
43
|
+
banner.operating_system_support_message_color = "light_green"
|
|
44
|
+
|
|
45
|
+
# create the options
|
|
46
|
+
options = Options()
|
|
47
|
+
|
|
48
|
+
options.option_description_color = "white"
|
|
49
|
+
options.option_number_color = "light_yellow"
|
|
50
|
+
options.option_name_color = "cyan"
|
|
51
|
+
options.navigation_option_color = "light_red"
|
|
52
|
+
|
|
53
|
+
options.add_option("View Profile", 1, "Profile")
|
|
54
|
+
options.add_option("Settings", 2, "Settings")
|
|
55
|
+
options.add_option("Help", 3, "Help")
|
|
56
|
+
|
|
57
|
+
# create the prompt
|
|
58
|
+
prompt = Prompt()
|
|
59
|
+
|
|
60
|
+
prompt.prompt_message_color = "light_magenta"
|
|
61
|
+
prompt.prompt_message = "Select an option: "
|
|
62
|
+
|
|
63
|
+
# create and run the menu
|
|
64
|
+
menu = Menu()
|
|
65
|
+
|
|
66
|
+
menu.name = "main"
|
|
67
|
+
menu.main_menu = True
|
|
68
|
+
|
|
69
|
+
menu.add_banner(banner)
|
|
70
|
+
menu.add_options(options)
|
|
71
|
+
menu.add_prompt(prompt)
|
|
72
|
+
|
|
73
|
+
menu.run()
|
|
74
|
+
|
|
75
|
+
# you can only simply display it (excludes the input prompt)
|
|
76
|
+
menu.display()
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
# [=== Imports ===] #
|
|
80
|
+
|
|
81
|
+
from colorama import Fore, Back
|
|
82
|
+
from functools import partial
|
|
83
|
+
import subprocess
|
|
84
|
+
import random
|
|
85
|
+
import sys
|
|
86
|
+
|
|
87
|
+
# [=== Global Variables ===]
|
|
88
|
+
|
|
89
|
+
# used to ensure the user does not give two or more Menu's the same name
|
|
90
|
+
_used_menu_name: list[str] = list()
|
|
91
|
+
|
|
92
|
+
# [=== Functions ===] #
|
|
93
|
+
|
|
94
|
+
def clear_terminal():
|
|
95
|
+
# for Linux, MacOS, and Unix-Like operating systems
|
|
96
|
+
if sys.platform in ("linux", "darwin"):
|
|
97
|
+
subprocess.run("clear", shell=True)
|
|
98
|
+
else:
|
|
99
|
+
# for Windows and other obsolete operating systems
|
|
100
|
+
subprocess.run("cls", shell=True)
|
|
101
|
+
|
|
102
|
+
def validate_foreground_color(fore_color: str) -> str:
|
|
103
|
+
foreground_colors = {
|
|
104
|
+
"blue": Fore.BLUE, "light_blue": Fore.LIGHTBLUE_EX,
|
|
105
|
+
"cyan": Fore.CYAN, "light_cyan": Fore.LIGHTCYAN_EX,
|
|
106
|
+
"red": Fore.RED, "light_red": Fore.LIGHTRED_EX,
|
|
107
|
+
"green": Fore.GREEN, "light_green": Fore.LIGHTGREEN_EX,
|
|
108
|
+
"yellow": Fore.YELLOW, "light_yellow": Fore.LIGHTYELLOW_EX,
|
|
109
|
+
"magenta": Fore.MAGENTA, "light_magenta": Fore.LIGHTMAGENTA_EX,
|
|
110
|
+
"black": Fore.BLACK, "white": Fore.WHITE,
|
|
111
|
+
"gray": Fore.LIGHTBLACK_EX, "grey": Fore.LIGHTBLACK_EX
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
color = fore_color.lower()
|
|
115
|
+
if color in foreground_colors:
|
|
116
|
+
return foreground_colors[color]
|
|
117
|
+
else:
|
|
118
|
+
print(f"{Fore.RED}[!] Error: {Fore.YELLOW}{color}{Fore.RED} is not a valid/supported color in this program.{Fore.RESET}")
|
|
119
|
+
print(f"{Fore.GREEN}The following colors are supported:{Fore.RESET}")
|
|
120
|
+
for color_key in foreground_colors.keys():
|
|
121
|
+
if color_key != "gray" or color_key != "grey":
|
|
122
|
+
print(f"{Fore.BLUE}{color_key}{Fore.RESET}")
|
|
123
|
+
else:
|
|
124
|
+
print(f"{Fore.BLUE}gray/grey{Fore.RESET}")
|
|
125
|
+
|
|
126
|
+
exit(1)
|
|
127
|
+
|
|
128
|
+
# note: although this function is currently not in use it may be later
|
|
129
|
+
def validate_background_color(back_color: str) -> str:
|
|
130
|
+
background_colors = {
|
|
131
|
+
"blue": Back.BLUE, "light_blue": Back.LIGHTBLUE_EX,
|
|
132
|
+
"cyan": Back.CYAN, "light_cyan": Back.LIGHTCYAN_EX,
|
|
133
|
+
"red": Back.RED, "light_red": Back.LIGHTRED_EX,
|
|
134
|
+
"green": Back.GREEN, "light_green": Back.LIGHTGREEN_EX,
|
|
135
|
+
"yellow": Back.YELLOW, "light_yellow": Back.LIGHTYELLOW_EX,
|
|
136
|
+
"magenta": Back.MAGENTA, "light_magenta": Back.LIGHTMAGENTA_EX,
|
|
137
|
+
"black": Back.BLACK, "white": Back.WHITE,
|
|
138
|
+
"gray": Back.LIGHTBLACK_EX, "grey": Back.LIGHTBLACK_EX
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
color = back_color.lower()
|
|
142
|
+
if color in background_colors:
|
|
143
|
+
return background_colors[color]
|
|
144
|
+
else:
|
|
145
|
+
print(f"{Fore.RED}[!] Error: {Fore.YELLOW}{color}{Fore.RED} is not a valid/supported color in this program.{Fore.RESET}")
|
|
146
|
+
print(f"{Fore.GREEN}The following colors are supported:{Fore.RESET}")
|
|
147
|
+
for color_key in background_colors.keys():
|
|
148
|
+
if color_key != "gray" or color_key != "grey":
|
|
149
|
+
print(f"{Fore.BLUE}{color_key}{Fore.RESET}")
|
|
150
|
+
else:
|
|
151
|
+
print(f"{Fore.BLUE}gray/grey{Fore.RESET}")
|
|
152
|
+
|
|
153
|
+
exit(1)
|
|
154
|
+
|
|
155
|
+
"""
|
|
156
|
+
note:
|
|
157
|
+
the reason for the complex approach in this function is to avoid large amounts of color repeats,
|
|
158
|
+
which was a major issue with a previous one-liner approach
|
|
159
|
+
"""
|
|
160
|
+
def paint_text(text: str, colors: list) -> str:
|
|
161
|
+
# avoids waisting compute time with an algorithm if there is only one color provided
|
|
162
|
+
if len(colors) == 1:
|
|
163
|
+
return f"{validate_foreground_color(colors[0])}{text}{Fore.RESET}"
|
|
164
|
+
|
|
165
|
+
# validates the colors and retrieves their actual color values
|
|
166
|
+
colors = [validate_foreground_color(fore_color=color) for color in colors]
|
|
167
|
+
# creates a list of every character in the given text
|
|
168
|
+
characters = list(text)
|
|
169
|
+
# keeps track of the colors list index numbers that can temporarily not be used
|
|
170
|
+
banned_indexes = set()
|
|
171
|
+
|
|
172
|
+
# len(colors) %2 == 0: even, else: odd
|
|
173
|
+
if len(colors) % 2 == 0:
|
|
174
|
+
# needed colors list index(s)
|
|
175
|
+
middle_index = len(colors) // 2
|
|
176
|
+
|
|
177
|
+
# note: used to determine the random index number
|
|
178
|
+
low = middle_index
|
|
179
|
+
high = middle_index
|
|
180
|
+
|
|
181
|
+
for char in range(len(text)):
|
|
182
|
+
# avoids waisting compute time on non-printable characters (with the needed exceptions)
|
|
183
|
+
if characters[char] == " ":
|
|
184
|
+
continue
|
|
185
|
+
elif characters[char].isprintable() == True or characters[char] == "\\" or characters[char] == "\'" or characters[char] == '\"':
|
|
186
|
+
pass
|
|
187
|
+
else:
|
|
188
|
+
continue
|
|
189
|
+
|
|
190
|
+
# note: loops until a valid index number has been selected
|
|
191
|
+
while True:
|
|
192
|
+
selected_index = random.randint(low, high)
|
|
193
|
+
|
|
194
|
+
if selected_index not in banned_indexes:
|
|
195
|
+
break
|
|
196
|
+
|
|
197
|
+
# add the color code for the current character
|
|
198
|
+
characters[char] = colors[selected_index] + characters[char]
|
|
199
|
+
|
|
200
|
+
if low != 0 and high != len(colors) - 1:
|
|
201
|
+
# ban the current low and high indexes temporarily
|
|
202
|
+
banned_indexes.add(low)
|
|
203
|
+
banned_indexes.add(high)
|
|
204
|
+
|
|
205
|
+
# fetch the new low and high indexes; note: center -> ends
|
|
206
|
+
low -= 1
|
|
207
|
+
high += 1
|
|
208
|
+
else:
|
|
209
|
+
# reset the low and high indexes values along with the banned_indexes set
|
|
210
|
+
low = middle_index
|
|
211
|
+
high = middle_index
|
|
212
|
+
|
|
213
|
+
banned_indexes = set()
|
|
214
|
+
|
|
215
|
+
# create the new text string
|
|
216
|
+
text = "".join(characters)
|
|
217
|
+
|
|
218
|
+
return f"{text}{Fore.RESET}"
|
|
219
|
+
else:
|
|
220
|
+
# needed colors list index(s)
|
|
221
|
+
middle_index = ((len(colors) - 1) // 2) + 1
|
|
222
|
+
|
|
223
|
+
# note: used to determine the random index number
|
|
224
|
+
low = 0
|
|
225
|
+
high = len(colors) - 1
|
|
226
|
+
|
|
227
|
+
for char in range(len(text)):
|
|
228
|
+
# avoids waisting compute time on non-printable characters (with the needed exceptions)
|
|
229
|
+
if characters[char] == " ":
|
|
230
|
+
continue
|
|
231
|
+
elif characters[char].isprintable() == True or characters[char] == "\\" or characters[char] == "\'" or characters[char] == '\"':
|
|
232
|
+
pass
|
|
233
|
+
else:
|
|
234
|
+
continue
|
|
235
|
+
|
|
236
|
+
# note: loops until a valid index number has been selected
|
|
237
|
+
while True:
|
|
238
|
+
selected_index = random.randint(low, high)
|
|
239
|
+
|
|
240
|
+
if selected_index not in banned_indexes:
|
|
241
|
+
break
|
|
242
|
+
|
|
243
|
+
# add the color code for the current character
|
|
244
|
+
characters[char] = colors[selected_index] + characters[char]
|
|
245
|
+
|
|
246
|
+
if low == middle_index or high == middle_index:
|
|
247
|
+
# reset the low and high indexes values along with the banned_indexes set
|
|
248
|
+
low = 0
|
|
249
|
+
high = len(colors) - 1
|
|
250
|
+
|
|
251
|
+
banned_indexes = set()
|
|
252
|
+
else:
|
|
253
|
+
# ban the current low and high indexes temporarily
|
|
254
|
+
banned_indexes.add(low)
|
|
255
|
+
banned_indexes.add(high)
|
|
256
|
+
|
|
257
|
+
# fetch the new low and high indexes; note: ends -> center
|
|
258
|
+
low += 1
|
|
259
|
+
high -= 1
|
|
260
|
+
|
|
261
|
+
# create the new text string
|
|
262
|
+
text = "".join(characters)
|
|
263
|
+
|
|
264
|
+
return f"{text}{Fore.RESET}"
|
|
265
|
+
|
|
266
|
+
def input_prompt(prompt_message) -> str:
|
|
267
|
+
try:
|
|
268
|
+
user_choice = input(prompt_message)
|
|
269
|
+
|
|
270
|
+
return user_choice
|
|
271
|
+
except KeyboardInterrupt:
|
|
272
|
+
print("\n")
|
|
273
|
+
exit(0)
|
|
274
|
+
|
|
275
|
+
# [=== Classes ===] #
|
|
276
|
+
|
|
277
|
+
class Banner:
|
|
278
|
+
def __init__(self):
|
|
279
|
+
# elements
|
|
280
|
+
self.title = str()
|
|
281
|
+
self.title_bar = str()
|
|
282
|
+
self.program_name = str()
|
|
283
|
+
self.program_version_number = float()
|
|
284
|
+
self.title_bar = str()
|
|
285
|
+
self.operating_system_support_message = "This Program Supports: Linux, MacOS, and Windows"
|
|
286
|
+
|
|
287
|
+
# element colors
|
|
288
|
+
self._title_colors: list[str] = list()
|
|
289
|
+
self._title_bar_color = str()
|
|
290
|
+
self._program_name_color = str()
|
|
291
|
+
self._program_version_number_color = str()
|
|
292
|
+
self._operating_system_support_message_color = str()
|
|
293
|
+
|
|
294
|
+
# --- getter/setter properties --- #
|
|
295
|
+
|
|
296
|
+
@property
|
|
297
|
+
def title_colors(self):
|
|
298
|
+
return self._title_colors
|
|
299
|
+
|
|
300
|
+
@title_colors.setter
|
|
301
|
+
def title_colors(self, colors: list):
|
|
302
|
+
# note: the paint function will handle the color validation for the colors list
|
|
303
|
+
self._title_colors = colors
|
|
304
|
+
|
|
305
|
+
@property
|
|
306
|
+
def title_bar_color(self):
|
|
307
|
+
return self._title_bar_color
|
|
308
|
+
|
|
309
|
+
@title_bar_color.setter
|
|
310
|
+
def title_bar_color(self, color: str):
|
|
311
|
+
self._title_bar_color = validate_foreground_color(color)
|
|
312
|
+
|
|
313
|
+
@property
|
|
314
|
+
def program_name_color(self):
|
|
315
|
+
return self._program_name_color
|
|
316
|
+
|
|
317
|
+
@program_name_color.setter
|
|
318
|
+
def program_name_color(self, color: str):
|
|
319
|
+
self._program_name_color = validate_foreground_color(color)
|
|
320
|
+
|
|
321
|
+
@property
|
|
322
|
+
def program_version_number_color(self):
|
|
323
|
+
return self._program_version_number_color
|
|
324
|
+
|
|
325
|
+
@program_version_number_color.setter
|
|
326
|
+
def program_version_number_color(self, color: str):
|
|
327
|
+
self._program_version_number_color = validate_foreground_color(color)
|
|
328
|
+
|
|
329
|
+
@property
|
|
330
|
+
def operating_system_support_message_color(self):
|
|
331
|
+
return self._operating_system_support_message_color
|
|
332
|
+
|
|
333
|
+
@operating_system_support_message_color.setter
|
|
334
|
+
def operating_system_support_message_color(self, color: str):
|
|
335
|
+
self._operating_system_support_message_color = validate_foreground_color(color)
|
|
336
|
+
|
|
337
|
+
class Options:
|
|
338
|
+
def __init__(self):
|
|
339
|
+
# elements #
|
|
340
|
+
self.options: dict[int, str] = dict()
|
|
341
|
+
|
|
342
|
+
# element colors
|
|
343
|
+
self._option_description_color = str()
|
|
344
|
+
self._option_number_color = str()
|
|
345
|
+
self._option_name_color = str()
|
|
346
|
+
|
|
347
|
+
self._navigation_option_color = Fore.LIGHTBLACK_EX
|
|
348
|
+
|
|
349
|
+
# --- getter/setter properties --- #
|
|
350
|
+
|
|
351
|
+
@property
|
|
352
|
+
def option_description_color(self):
|
|
353
|
+
return self._option_description_color
|
|
354
|
+
|
|
355
|
+
@option_description_color.setter
|
|
356
|
+
def option_description_color(self, color: str):
|
|
357
|
+
self._option_description_color = validate_foreground_color(color)
|
|
358
|
+
|
|
359
|
+
@property
|
|
360
|
+
def option_number_color(self):
|
|
361
|
+
return self._option_number_color
|
|
362
|
+
|
|
363
|
+
@option_number_color.setter
|
|
364
|
+
def option_number_color(self, color: str):
|
|
365
|
+
self._option_number_color = validate_foreground_color(color)
|
|
366
|
+
|
|
367
|
+
@property
|
|
368
|
+
def option_name_color(self):
|
|
369
|
+
return self._option_name_color
|
|
370
|
+
|
|
371
|
+
@option_name_color.setter
|
|
372
|
+
def option_name_color(self, color: str):
|
|
373
|
+
self._option_name_color = validate_foreground_color(color)
|
|
374
|
+
|
|
375
|
+
@property
|
|
376
|
+
def navigation_option_color(self):
|
|
377
|
+
return self._navigation_option_color
|
|
378
|
+
|
|
379
|
+
@navigation_option_color.setter
|
|
380
|
+
def navigation_option_color(self, color: str):
|
|
381
|
+
self._navigation_option_color = validate_foreground_color(color)
|
|
382
|
+
|
|
383
|
+
# --- methods --- #
|
|
384
|
+
|
|
385
|
+
def add_option(self, option_description: str, option_number: int, option_name: str) -> None:
|
|
386
|
+
# validation
|
|
387
|
+
if option_number == 0:
|
|
388
|
+
print(f"{Fore.RED}[!] Error: The {Fore.YELLOW}option_number 0{Fore.RED} is reserved for the exit option (automatically included).{Fore.RESET}")
|
|
389
|
+
sys.exit(1)
|
|
390
|
+
|
|
391
|
+
if option_number < 0:
|
|
392
|
+
print(f"{Fore.RED}[!] Error: The chosen {Fore.YELLOW}option_number{Fore.RED} can not be negative.")
|
|
393
|
+
|
|
394
|
+
if option_number in self.options.keys():
|
|
395
|
+
print(f"{Fore.RED}[!] Error: The {Fore.YELLOW}option_number {option_number}{Fore.RED} is already in use.{Fore.RESET}")
|
|
396
|
+
|
|
397
|
+
# construct and add the option to the options dictionary; note: color reset is handled when the element is added to a Menu
|
|
398
|
+
option = f"{self._option_description_color}{option_description}\n{self.option_number_color}{option_number}) {self._option_name_color}{option_name}"
|
|
399
|
+
|
|
400
|
+
self.options[option_number] = option
|
|
401
|
+
|
|
402
|
+
class Prompt:
|
|
403
|
+
def __int__(self):
|
|
404
|
+
# element(s)
|
|
405
|
+
self.prompt_message = "Choose An Option From The Menu [ex: 0]: "
|
|
406
|
+
|
|
407
|
+
# element color(s)
|
|
408
|
+
self._prompt_message_color = str()
|
|
409
|
+
|
|
410
|
+
# used to determine wether or not the user decided to color the prompt message using the module
|
|
411
|
+
# note: they may have chosen not to because they wanted to use multiple colors, so instead they added the color codes themself
|
|
412
|
+
self.prompt_colored = False
|
|
413
|
+
|
|
414
|
+
# --- getter/setter properties --- #
|
|
415
|
+
|
|
416
|
+
@property
|
|
417
|
+
def prompt_message_color(self):
|
|
418
|
+
return self._prompt_message_color
|
|
419
|
+
|
|
420
|
+
@prompt_message_color.setter
|
|
421
|
+
def prompt_message_color(self, color: str):
|
|
422
|
+
self._prompt_message_color = validate_foreground_color(color)
|
|
423
|
+
|
|
424
|
+
self.prompt_colored = True
|
|
425
|
+
|
|
426
|
+
class Menu:
|
|
427
|
+
def __init__(self):
|
|
428
|
+
# note: these strings are formated to construct menu elements (in the technical sense they are the elements)
|
|
429
|
+
self._menu = str()
|
|
430
|
+
self._banner = "{title}\n{title_bar_color}{title_bar}\n{program_name_color}{program_name}{program_version_number_color}v{program_version_number}\n{operating_system_support_message_color}{operating_system_support_message}\n{reset}"
|
|
431
|
+
self._option = "{option}{reset}"
|
|
432
|
+
|
|
433
|
+
# this variable will a hold a callable address for the input_prompt() function loaded with its parameter already
|
|
434
|
+
self._prompt = None
|
|
435
|
+
|
|
436
|
+
# used to make it easier to sort through the final Menataur (menu interface) data
|
|
437
|
+
self._name = str()
|
|
438
|
+
# used to determine wether or not this is a/the main menu
|
|
439
|
+
self.main_menu = False
|
|
440
|
+
# used to determine wether or not this is the final menu in a menu path
|
|
441
|
+
self._final_menu = False
|
|
442
|
+
# used to store the option numbers for basic input validation in the run_menu method
|
|
443
|
+
self._option_numbers: set[int] = set()
|
|
444
|
+
# used to store the Option to Menu mappings - required if the user want's to use this Menu in a Menataur
|
|
445
|
+
self._datamap: dict[int, Menu | str] = dict()
|
|
446
|
+
|
|
447
|
+
# --- getter/setter properties --- #
|
|
448
|
+
|
|
449
|
+
@property
|
|
450
|
+
def name(self):
|
|
451
|
+
return self._name
|
|
452
|
+
|
|
453
|
+
@name.setter
|
|
454
|
+
def name(self, menu_name: str):
|
|
455
|
+
if menu_name in _used_menu_name:
|
|
456
|
+
print(f"{Fore.RED}[!] Error: Every Menu must be assigned a unique {Fore.YELLOW}name{Fore.RED}.{Fore.RESET}")
|
|
457
|
+
exit(1)
|
|
458
|
+
|
|
459
|
+
_used_menu_name.append(menu_name)
|
|
460
|
+
|
|
461
|
+
self._name = menu_name
|
|
462
|
+
|
|
463
|
+
@property
|
|
464
|
+
def final_menu(self):
|
|
465
|
+
return self._final_menu
|
|
466
|
+
|
|
467
|
+
@final_menu.setter
|
|
468
|
+
def final_menu(self, value: bool):
|
|
469
|
+
if value == True:
|
|
470
|
+
# used to create a special data map for the final menu
|
|
471
|
+
_final_menu_datamap: dict[int, str] = dict()
|
|
472
|
+
# used to know when to stop looping; note: 1 is not subtracted for previous menu because of the way the range() function works
|
|
473
|
+
_last_option = max(self._option_numbers)
|
|
474
|
+
|
|
475
|
+
_final_menu_datamap[0] = "exit"
|
|
476
|
+
|
|
477
|
+
for opt in range(1, _last_option):
|
|
478
|
+
_final_menu_datamap[opt] = "final_menu"
|
|
479
|
+
|
|
480
|
+
_final_menu_datamap[_last_option] = "previous_menu"
|
|
481
|
+
|
|
482
|
+
self._datamap = _final_menu_datamap
|
|
483
|
+
|
|
484
|
+
# set the _final_menu tracker variable to the user given bool (True or False)
|
|
485
|
+
self._final_menu = value
|
|
486
|
+
|
|
487
|
+
@property
|
|
488
|
+
def datamap(self):
|
|
489
|
+
return self._datamap
|
|
490
|
+
|
|
491
|
+
@datamap.setter
|
|
492
|
+
def datamap(self, map: dict):
|
|
493
|
+
# add the exit and previous menu (if applicable) options
|
|
494
|
+
map[0] = "exit"
|
|
495
|
+
|
|
496
|
+
# ensure this is not a final menu because final Menus do not need a user configured datamap
|
|
497
|
+
if self.final_menu == True:
|
|
498
|
+
print(f"{Fore.RED}[!] Error: This is a {Fore.YELLOW}Final Menu{Fore.RED}, it should not have a datamap.{Fore.RESET}")
|
|
499
|
+
exit(1)
|
|
500
|
+
|
|
501
|
+
if self.main_menu != True:
|
|
502
|
+
map[max(self._option_numbers)] = "previous_menu"
|
|
503
|
+
|
|
504
|
+
self._datamap = map
|
|
505
|
+
|
|
506
|
+
# ensure the user has provided map keys that match the option numbers and store the map if so
|
|
507
|
+
if map.keys() != self._datamap.keys():
|
|
508
|
+
print(f"{Fore.RED}[!] Error: The {Fore.YELLOW}map keys{Fore.RED} do not match the {Fore.YELLOW}option numbers{Fore.RED}.{Fore.RESET}")
|
|
509
|
+
print(map.keys())
|
|
510
|
+
print(self._datamap.keys())
|
|
511
|
+
exit(1)
|
|
512
|
+
|
|
513
|
+
# --- methods --- #
|
|
514
|
+
|
|
515
|
+
# formally adds an element to the menu
|
|
516
|
+
def _add_element(self, menu_element) -> None:
|
|
517
|
+
self._menu = self._menu + f"{menu_element}\n"
|
|
518
|
+
|
|
519
|
+
def add_banner(self, banner: Banner) -> None:
|
|
520
|
+
self._add_element(menu_element=self._banner.format(
|
|
521
|
+
title=paint_text(banner.title, banner.title_colors),
|
|
522
|
+
title_bar_color=banner.title_bar_color,
|
|
523
|
+
title_bar=banner.title_bar,
|
|
524
|
+
program_name_color=banner.program_name_color,
|
|
525
|
+
program_name=banner.program_name,
|
|
526
|
+
program_version_number_color=banner.program_version_number_color,
|
|
527
|
+
program_version_number=banner.program_version_number,
|
|
528
|
+
operating_system_support_message_color=banner.operating_system_support_message_color,
|
|
529
|
+
operating_system_support_message=banner.operating_system_support_message,
|
|
530
|
+
reset=Fore.RESET
|
|
531
|
+
))
|
|
532
|
+
|
|
533
|
+
def add_options(self, options: Options) -> None:
|
|
534
|
+
# add the exit option
|
|
535
|
+
exit_option = f"{options.navigation_option_color}0) Exit"
|
|
536
|
+
|
|
537
|
+
self._add_element(self._option.format(
|
|
538
|
+
option=exit_option,
|
|
539
|
+
reset=Fore.RESET
|
|
540
|
+
))
|
|
541
|
+
|
|
542
|
+
for option_key in options.options.keys():
|
|
543
|
+
self._add_element(self._option.format(
|
|
544
|
+
option=options.options[option_key],
|
|
545
|
+
reset=Fore.RESET
|
|
546
|
+
))
|
|
547
|
+
|
|
548
|
+
# add the previous menu option if necessary
|
|
549
|
+
if self.main_menu != True:
|
|
550
|
+
previous_menu_option_number = list(options.options.keys())[-1] + 1
|
|
551
|
+
previous_menu_option = f"{options.navigation_option_color}{previous_menu_option_number}) Previous Menu"
|
|
552
|
+
|
|
553
|
+
self._add_element(self._option.format(
|
|
554
|
+
option=previous_menu_option,
|
|
555
|
+
reset=Fore.RESET
|
|
556
|
+
))
|
|
557
|
+
|
|
558
|
+
# add the previous_menu_option with its respective option number to the options dictionary to keep a record of it
|
|
559
|
+
options.options[previous_menu_option_number] = previous_menu_option
|
|
560
|
+
|
|
561
|
+
# add the exit_option with it's respective option number to the options dictionary to keep a record of it
|
|
562
|
+
options.options[0] = exit_option
|
|
563
|
+
|
|
564
|
+
# store the option numbers to reference for basic input validation in the run_method function
|
|
565
|
+
self._option_numbers = options.options.keys()
|
|
566
|
+
|
|
567
|
+
def add_prompt(self, prompt: Prompt) -> None:
|
|
568
|
+
if prompt.prompt_colored == True:
|
|
569
|
+
prompt_message = f"{prompt.prompt_message_color}{prompt.prompt_message}{Fore.RESET}"
|
|
570
|
+
else:
|
|
571
|
+
# note: the Fore.RESET is added in case the user has forgotten to include the reset code (in the case that they manually added the color codes in their prompt_message)
|
|
572
|
+
prompt_message = f"{prompt.prompt_message}{Fore.RESET}"
|
|
573
|
+
|
|
574
|
+
# creates a callable function object with the parameter; think of it like a loaded gun ready to fire
|
|
575
|
+
self._prompt = partial(input_prompt, prompt_message)
|
|
576
|
+
|
|
577
|
+
def display(self) -> None:
|
|
578
|
+
print(self._menu)
|
|
579
|
+
|
|
580
|
+
def run(self) -> int:
|
|
581
|
+
print(self._menu)
|
|
582
|
+
|
|
583
|
+
if self.name == str():
|
|
584
|
+
print(f"{Fore.RED}[!] Error: Every Menu must be assigned a unique{Fore.YELLOW}name{Fore.RED}.")
|
|
585
|
+
exit(1)
|
|
586
|
+
|
|
587
|
+
while True:
|
|
588
|
+
user_choice = self._prompt()
|
|
589
|
+
|
|
590
|
+
try:
|
|
591
|
+
user_choice = int(user_choice)
|
|
592
|
+
|
|
593
|
+
if user_choice == 0:
|
|
594
|
+
exit(0)
|
|
595
|
+
|
|
596
|
+
if user_choice in self._option_numbers:
|
|
597
|
+
clear_terminal()
|
|
598
|
+
|
|
599
|
+
return user_choice
|
|
600
|
+
else:
|
|
601
|
+
print(f"{Fore.RED}Invalid Option!{Fore.RESET}")
|
|
602
|
+
except ValueError:
|
|
603
|
+
print(f"{Fore.RED}Invalid Option!{Fore.RESET}")
|
|
604
|
+
except Exception as e:
|
|
605
|
+
print(f"{Fore.RED}Invalid Option!{Fore.RESET}")
|
|
606
|
+
|
|
607
|
+
class Menataur:
|
|
608
|
+
def __init__(self):
|
|
609
|
+
# stores the starting position of the Menataur (menu interface)
|
|
610
|
+
self._start_menu = None
|
|
611
|
+
|
|
612
|
+
@property
|
|
613
|
+
def start_menu(self):
|
|
614
|
+
return self._start_menu
|
|
615
|
+
|
|
616
|
+
@start_menu.setter
|
|
617
|
+
def start_menu(self, menu: Menu):
|
|
618
|
+
if menu.main_menu == True:
|
|
619
|
+
self._start_menu = menu
|
|
620
|
+
else:
|
|
621
|
+
print(f"{Fore.RED}[!] Error: The {Fore.YELLOW}start_menu{Fore.RED} must be a {Fore.YELLOW}main_menu{Fore.RED}.{Fore.RESET}")
|
|
622
|
+
print(f"{Fore.BLUE}[?] Help: If you would like to use this {Fore.YELLOW}menu{Fore.BLUE} as your start_menu, set {Fore.YELLOW}main_menu=True{Fore.BLUE}.{Fore.RESET}")
|
|
623
|
+
exit(1)
|
|
624
|
+
|
|
625
|
+
def run(self) -> list[int]:
|
|
626
|
+
# keeps track of the current menu (changed at the end of each loop depending on the user choice)
|
|
627
|
+
_current_menu = self._start_menu
|
|
628
|
+
# keeps track of the previous menu (becomes _current_menu after the _current_menu runs in each loop)
|
|
629
|
+
_previous_menu = None
|
|
630
|
+
|
|
631
|
+
"""
|
|
632
|
+
note:
|
|
633
|
+
This data format was chosen because it allows for easy access to the data
|
|
634
|
+
while still maintaing the orignal order the data was added. If just was used it would make
|
|
635
|
+
it hard for the user to sort through the data and if just a dictionary was used the .keys() and
|
|
636
|
+
.values() sets would not retain the original order in which the data was addded.
|
|
637
|
+
"""
|
|
638
|
+
_user_choices: list[dict[str, int]] = list()
|
|
639
|
+
|
|
640
|
+
if self._start_menu == None:
|
|
641
|
+
print(f"{Fore.RED}[!] Error: You must set a {Fore.YELLOW}start_menu{Fore.RED}.{Fore.RESET}")
|
|
642
|
+
|
|
643
|
+
# note: think of a Binary Tree when thiking about how the menu navigation works
|
|
644
|
+
while _current_menu != "final_menu":
|
|
645
|
+
# fetch the current Menu's name
|
|
646
|
+
_current_menu_name = _current_menu.name
|
|
647
|
+
|
|
648
|
+
# fetch the current Menu's datamap
|
|
649
|
+
_current_menu_datamap = _current_menu.datamap
|
|
650
|
+
|
|
651
|
+
# run the current menu and store the returned option number
|
|
652
|
+
_menu_output = _current_menu.run()
|
|
653
|
+
|
|
654
|
+
# use the _menu_output to determine what to do next; note: the datamap may provide another Menu or a Menu code
|
|
655
|
+
_next = _current_menu_datamap[_menu_output]
|
|
656
|
+
|
|
657
|
+
match _next:
|
|
658
|
+
case "final_menu":
|
|
659
|
+
clear_terminal()
|
|
660
|
+
|
|
661
|
+
_user_choice_map = {_current_menu_name: _menu_output}
|
|
662
|
+
_user_choices.append(_user_choice_map)
|
|
663
|
+
|
|
664
|
+
return _user_choices
|
|
665
|
+
case "previous_menu":
|
|
666
|
+
clear_terminal()
|
|
667
|
+
|
|
668
|
+
# the _current_menu was already stored as the _previous_menu, so nothing needs to be done
|
|
669
|
+
continue
|
|
670
|
+
case _:
|
|
671
|
+
clear_terminal()
|
|
672
|
+
|
|
673
|
+
# set the previous_menu for the _next Menu's datamap
|
|
674
|
+
_next.datamap[max(_next.datamap.keys())] = _current_menu
|
|
675
|
+
|
|
676
|
+
# add the user's choice to the _user_choices_map
|
|
677
|
+
_user_choice_map = {_current_menu_name: _menu_output}
|
|
678
|
+
_user_choices.append(_user_choice_map)
|
|
679
|
+
|
|
680
|
+
# set the _current_menu to the _next Menu so the _next Menu is called in the next loop
|
|
681
|
+
_current_menu = _next
|
|
682
|
+
|
|
683
|
+
continue
|
menataur-0.1.0/setup.cfg
ADDED