prettier-console 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.
- prettier_console-0.1.0/PKG-INFO +28 -0
- prettier_console-0.1.0/README.md +15 -0
- prettier_console-0.1.0/prettier_console/__init__.py +16 -0
- prettier_console-0.1.0/prettier_console/ascii_art_font/__init__.py +8 -0
- prettier_console-0.1.0/prettier_console/ascii_art_font/ascii_art_font.py +128 -0
- prettier_console-0.1.0/prettier_console/ascii_art_font/font/banner.json +218 -0
- prettier_console-0.1.0/prettier_console/ascii_art_font/font/header.json +137 -0
- prettier_console-0.1.0/prettier_console/interactive.py +408 -0
- prettier_console-0.1.0/prettier_console/manage.py +63 -0
- prettier_console-0.1.0/prettier_console.egg-info/PKG-INFO +28 -0
- prettier_console-0.1.0/prettier_console.egg-info/SOURCES.txt +17 -0
- prettier_console-0.1.0/prettier_console.egg-info/dependency_links.txt +1 -0
- prettier_console-0.1.0/prettier_console.egg-info/entry_points.txt +2 -0
- prettier_console-0.1.0/prettier_console.egg-info/requires.txt +1 -0
- prettier_console-0.1.0/prettier_console.egg-info/top_level.txt +3 -0
- prettier_console-0.1.0/pyproject.toml +31 -0
- prettier_console-0.1.0/setup.cfg +4 -0
- prettier_console-0.1.0/tests/ascii-art-test.py +7 -0
- prettier_console-0.1.0/tests/testcases.py +49 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: prettier_console
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Provide you a automation to construct a prettier interactive console in one call
|
|
5
|
+
Author-email: MrCosine <a95sun@uwaterloo.ca>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/mr-cosine/prettier_console
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Requires-Python: >=3.8
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
Requires-Dist: keyboard>=0.13.5
|
|
13
|
+
|
|
14
|
+
So yea some shortcuts for you to get a prettier interactive console. Um I think its good for lazy people. If you enjoy you enjoy and if you don't...you don't.
|
|
15
|
+
Will update this readme later on... I guess.
|
|
16
|
+
|
|
17
|
+
What you can do:
|
|
18
|
+
1. using interactive.menu() to create menus with single selections
|
|
19
|
+
2. Customize your page by inserting interactive parts using tools such as print_selections and print_yesorno.
|
|
20
|
+
3. draw ascii-art banners and headers
|
|
21
|
+
4. get colored outputs
|
|
22
|
+
|
|
23
|
+
dependencies:
|
|
24
|
+
build==1.5.0
|
|
25
|
+
colorama==0.4.6
|
|
26
|
+
keyboard==0.13.5
|
|
27
|
+
packaging==26.2
|
|
28
|
+
pyproject_hooks==1.2.0
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
So yea some shortcuts for you to get a prettier interactive console. Um I think its good for lazy people. If you enjoy you enjoy and if you don't...you don't.
|
|
2
|
+
Will update this readme later on... I guess.
|
|
3
|
+
|
|
4
|
+
What you can do:
|
|
5
|
+
1. using interactive.menu() to create menus with single selections
|
|
6
|
+
2. Customize your page by inserting interactive parts using tools such as print_selections and print_yesorno.
|
|
7
|
+
3. draw ascii-art banners and headers
|
|
8
|
+
4. get colored outputs
|
|
9
|
+
|
|
10
|
+
dependencies:
|
|
11
|
+
build==1.5.0
|
|
12
|
+
colorama==0.4.6
|
|
13
|
+
keyboard==0.13.5
|
|
14
|
+
packaging==26.2
|
|
15
|
+
pyproject_hooks==1.2.0
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from .interactive import (
|
|
2
|
+
colored_output,
|
|
3
|
+
default_colored_output,
|
|
4
|
+
display_width,
|
|
5
|
+
safe_input,
|
|
6
|
+
clear_screen,
|
|
7
|
+
select_files,
|
|
8
|
+
select_folder,
|
|
9
|
+
print_selections,
|
|
10
|
+
print_yesorno,
|
|
11
|
+
print_banner,
|
|
12
|
+
print_header,
|
|
13
|
+
menu,
|
|
14
|
+
home_menu,
|
|
15
|
+
quit_program
|
|
16
|
+
)
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import string
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
def _get_cset(style):
|
|
6
|
+
script_dir = os.path.dirname(os.path.abspath(__file__))
|
|
7
|
+
try:
|
|
8
|
+
cset = json.load(open(os.path.join(script_dir, 'font', f'{style}.json'), 'r', encoding='utf-8'))
|
|
9
|
+
except FileNotFoundError:
|
|
10
|
+
raise Exception(f"Invalid style {style}: not supported by the font library")
|
|
11
|
+
except Exception as e:
|
|
12
|
+
raise Exception(f"Error loading style {style}: {str(e)}")
|
|
13
|
+
return cset
|
|
14
|
+
|
|
15
|
+
def get_character(char, style):
|
|
16
|
+
if len(char) > 1 or not isinstance(char, str): raise Exception("Chracter invalid: must be a character")
|
|
17
|
+
char = char.upper()
|
|
18
|
+
|
|
19
|
+
cset = _get_cset(style)
|
|
20
|
+
output_char = cset.get(char, None)
|
|
21
|
+
if output_char is None:
|
|
22
|
+
missing = 'space' if char == ' ' else char
|
|
23
|
+
raise Exception(f'File error: incomplete ascii character library. Missing character: {missing}')
|
|
24
|
+
|
|
25
|
+
return output_char
|
|
26
|
+
|
|
27
|
+
def build_display(input_string, style, delim=''):
|
|
28
|
+
if len(input_string) < 1: input_string = ' '
|
|
29
|
+
|
|
30
|
+
all_characters = set(input_string.upper())
|
|
31
|
+
cset = {}
|
|
32
|
+
for c in all_characters: cset.update({c: get_character(c, style)})
|
|
33
|
+
|
|
34
|
+
print_string = [c.upper() for c in input_string]
|
|
35
|
+
lines = []
|
|
36
|
+
line = 1
|
|
37
|
+
while all(cset.get(c).get(str(line), None) is not None for c in cset):
|
|
38
|
+
lines.append(delim.join(cset.get(char).get(str(line)) for char in print_string))
|
|
39
|
+
line += 1
|
|
40
|
+
|
|
41
|
+
width = len(lines[0]) if lines else 0
|
|
42
|
+
return '\n'.join(lines), width
|
|
43
|
+
|
|
44
|
+
def parse_font_file(path):
|
|
45
|
+
"""
|
|
46
|
+
parse a text file into a character set dictionary for a font style.
|
|
47
|
+
|
|
48
|
+
each line is one row of the glyphs: the 26 letters (A-Z) followed by the
|
|
49
|
+
space character, each segment separated by '/'.
|
|
50
|
+
|
|
51
|
+
:param path: string, path to the font text file
|
|
52
|
+
|
|
53
|
+
:return: dict, {char: {'1': string, '2': string, ...}}
|
|
54
|
+
"""
|
|
55
|
+
with open(path, 'r', encoding='utf-8') as f:
|
|
56
|
+
lines = f.read().splitlines()
|
|
57
|
+
|
|
58
|
+
if len(lines) == 0:
|
|
59
|
+
raise Exception('Font file error: file is empty')
|
|
60
|
+
|
|
61
|
+
characters = list(string.ascii_uppercase) + [' ']
|
|
62
|
+
cset = {character: {} for character in characters}
|
|
63
|
+
|
|
64
|
+
for i, line in enumerate(lines):
|
|
65
|
+
segments = line.split('/')
|
|
66
|
+
if len(segments) != len(characters):
|
|
67
|
+
raise Exception(
|
|
68
|
+
f"Font file error: line {i + 1} has {len(segments)} '/'-separated segments, "
|
|
69
|
+
f'expected {len(characters)} (A-Z + space).'
|
|
70
|
+
)
|
|
71
|
+
for character, segment in zip(characters, segments):
|
|
72
|
+
cset[character][str(i + 1)] = segment
|
|
73
|
+
|
|
74
|
+
return cset
|
|
75
|
+
|
|
76
|
+
def set_cset(style, font_path):
|
|
77
|
+
"""
|
|
78
|
+
parse a font text file and add or replace a style under ascii_art_font/font/.
|
|
79
|
+
|
|
80
|
+
:param style: string, the style key to save the parsed font under
|
|
81
|
+
:param font_path: string, path to the font text file
|
|
82
|
+
"""
|
|
83
|
+
script_dir = os.path.dirname(os.path.abspath(__file__))
|
|
84
|
+
font_dir = os.path.join(script_dir, 'font')
|
|
85
|
+
os.makedirs(font_dir, exist_ok=True)
|
|
86
|
+
json_path = os.path.join(font_dir, f'{style}.json')
|
|
87
|
+
|
|
88
|
+
data = parse_font_file(font_path)
|
|
89
|
+
|
|
90
|
+
with open(json_path, 'w', encoding='utf-8') as f:
|
|
91
|
+
json.dump(data, f, ensure_ascii=False, indent=4)
|
|
92
|
+
|
|
93
|
+
def _build_default_display(input_string, style):
|
|
94
|
+
"""
|
|
95
|
+
build the rendered content of a default style, underlined by a separator.
|
|
96
|
+
|
|
97
|
+
:param input_string: string, the text to render, '\\n' splits it into rows
|
|
98
|
+
:param style: string, the font style to render with
|
|
99
|
+
|
|
100
|
+
:return: string, the rendered content, rows joined by '\\n'
|
|
101
|
+
"""
|
|
102
|
+
lines = []
|
|
103
|
+
length = 0
|
|
104
|
+
for line in input_string.split('\n'):
|
|
105
|
+
display_content, length = build_display(line, style, delim=' ')
|
|
106
|
+
lines.append(display_content)
|
|
107
|
+
lines.append('='*length)
|
|
108
|
+
return '\n'.join(lines)
|
|
109
|
+
|
|
110
|
+
def default_banner(input_string):
|
|
111
|
+
"""
|
|
112
|
+
build the banner content.
|
|
113
|
+
|
|
114
|
+
:param input_string: string, the text to render
|
|
115
|
+
|
|
116
|
+
:return: string, the rendered banner content
|
|
117
|
+
"""
|
|
118
|
+
return _build_default_display(input_string, 'banner')
|
|
119
|
+
|
|
120
|
+
def default_header(input_string):
|
|
121
|
+
"""
|
|
122
|
+
build the header content.
|
|
123
|
+
|
|
124
|
+
:param input_string: string, the text to render
|
|
125
|
+
|
|
126
|
+
:return: string, the rendered header content
|
|
127
|
+
"""
|
|
128
|
+
return _build_default_display(input_string, 'header')
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
{
|
|
2
|
+
"A": {
|
|
3
|
+
"1": " █████╗ ",
|
|
4
|
+
"2": "██╔══██╗",
|
|
5
|
+
"3": "███████║",
|
|
6
|
+
"4": "██╔══██║",
|
|
7
|
+
"5": "██║ ██║",
|
|
8
|
+
"6": "╚═╝ ╚═╝"
|
|
9
|
+
},
|
|
10
|
+
"B": {
|
|
11
|
+
"1": "██████╗ ",
|
|
12
|
+
"2": "██╔══██╗",
|
|
13
|
+
"3": "██████╔╝",
|
|
14
|
+
"4": "██╔══██╗",
|
|
15
|
+
"5": "██████╔╝",
|
|
16
|
+
"6": "╚═════╝ "
|
|
17
|
+
},
|
|
18
|
+
"C": {
|
|
19
|
+
"1": " ██████╗",
|
|
20
|
+
"2": "██╔════╝",
|
|
21
|
+
"3": "██║ ",
|
|
22
|
+
"4": "██║ ",
|
|
23
|
+
"5": "╚██████╗",
|
|
24
|
+
"6": " ╚═════╝"
|
|
25
|
+
},
|
|
26
|
+
"D": {
|
|
27
|
+
"1": "██████╗ ",
|
|
28
|
+
"2": "██╔══██╗",
|
|
29
|
+
"3": "██║ ██║",
|
|
30
|
+
"4": "██║ ██║",
|
|
31
|
+
"5": "██████╔╝",
|
|
32
|
+
"6": "╚═════╝ "
|
|
33
|
+
},
|
|
34
|
+
"E": {
|
|
35
|
+
"1": "███████╗",
|
|
36
|
+
"2": "██╔════╝",
|
|
37
|
+
"3": "█████╗ ",
|
|
38
|
+
"4": "██╔══╝ ",
|
|
39
|
+
"5": "███████╗",
|
|
40
|
+
"6": "╚══════╝"
|
|
41
|
+
},
|
|
42
|
+
"F": {
|
|
43
|
+
"1": "███████╗",
|
|
44
|
+
"2": "██╔════╝",
|
|
45
|
+
"3": "█████╗ ",
|
|
46
|
+
"4": "██╔══╝ ",
|
|
47
|
+
"5": "██║ ",
|
|
48
|
+
"6": "╚═╝ "
|
|
49
|
+
},
|
|
50
|
+
"G": {
|
|
51
|
+
"1": " ██████╗ ",
|
|
52
|
+
"2": "██╔════╝ ",
|
|
53
|
+
"3": "██║ ███╗",
|
|
54
|
+
"4": "██║ ██║",
|
|
55
|
+
"5": "╚██████╔╝",
|
|
56
|
+
"6": " ╚═════╝ "
|
|
57
|
+
},
|
|
58
|
+
"H": {
|
|
59
|
+
"1": "██╗ ██╗",
|
|
60
|
+
"2": "██║ ██║",
|
|
61
|
+
"3": "███████║",
|
|
62
|
+
"4": "██╔══██║",
|
|
63
|
+
"5": "██║ ██║",
|
|
64
|
+
"6": "╚═╝ ╚═╝"
|
|
65
|
+
},
|
|
66
|
+
"I": {
|
|
67
|
+
"1": "████╗",
|
|
68
|
+
"2": "╚██╔╝",
|
|
69
|
+
"3": " ██║ ",
|
|
70
|
+
"4": " ██║ ",
|
|
71
|
+
"5": "████╗",
|
|
72
|
+
"6": "╚═══╝"
|
|
73
|
+
},
|
|
74
|
+
"J": {
|
|
75
|
+
"1": "██╗ ",
|
|
76
|
+
"2": "██║ ",
|
|
77
|
+
"3": "██║ ",
|
|
78
|
+
"4": "██║ ██║",
|
|
79
|
+
"5": "╚█████╔╝",
|
|
80
|
+
"6": " ╚════╝ "
|
|
81
|
+
},
|
|
82
|
+
"K": {
|
|
83
|
+
"1": "██╗ ██╗",
|
|
84
|
+
"2": "██║ ██╔╝",
|
|
85
|
+
"3": "█████╔╝ ",
|
|
86
|
+
"4": "██╔═██╗ ",
|
|
87
|
+
"5": "██║ ╚██╗",
|
|
88
|
+
"6": "╚═╝ ╚═╝"
|
|
89
|
+
},
|
|
90
|
+
"L": {
|
|
91
|
+
"1": "██╗ ",
|
|
92
|
+
"2": "██║ ",
|
|
93
|
+
"3": "██║ ",
|
|
94
|
+
"4": "██║ ",
|
|
95
|
+
"5": "███████╗",
|
|
96
|
+
"6": "╚══════╝"
|
|
97
|
+
},
|
|
98
|
+
"M": {
|
|
99
|
+
"1": "███╗ ███╗",
|
|
100
|
+
"2": "████╗ ████║",
|
|
101
|
+
"3": "██╔████╔██║",
|
|
102
|
+
"4": "██║╚██╔╝██║",
|
|
103
|
+
"5": "██║ ╚═╝ ██║",
|
|
104
|
+
"6": "╚═╝ ╚═╝"
|
|
105
|
+
},
|
|
106
|
+
"N": {
|
|
107
|
+
"1": "███╗ ██╗",
|
|
108
|
+
"2": "████╗ ██║",
|
|
109
|
+
"3": "██╔██╗ ██║",
|
|
110
|
+
"4": "██║╚██╗██║",
|
|
111
|
+
"5": "██║ ╚████║",
|
|
112
|
+
"6": "╚═╝ ╚═══╝"
|
|
113
|
+
},
|
|
114
|
+
"O": {
|
|
115
|
+
"1": " ██████╗ ",
|
|
116
|
+
"2": "██╔═══██╗",
|
|
117
|
+
"3": "██║ ██║",
|
|
118
|
+
"4": "██║ ██║",
|
|
119
|
+
"5": "╚██████╔╝",
|
|
120
|
+
"6": " ╚═════╝ "
|
|
121
|
+
},
|
|
122
|
+
"P": {
|
|
123
|
+
"1": "██████╗ ",
|
|
124
|
+
"2": "██╔══██╗",
|
|
125
|
+
"3": "██████╔╝",
|
|
126
|
+
"4": "██╔═══╝ ",
|
|
127
|
+
"5": "██║ ",
|
|
128
|
+
"6": "╚═╝ "
|
|
129
|
+
},
|
|
130
|
+
"Q": {
|
|
131
|
+
"1": " ██████╗ ",
|
|
132
|
+
"2": "██╔═══██╗",
|
|
133
|
+
"3": "██║ ██║",
|
|
134
|
+
"4": "██║▄▄ ██║",
|
|
135
|
+
"5": "╚██████╔╝",
|
|
136
|
+
"6": " ╚══▀▀═╝ "
|
|
137
|
+
},
|
|
138
|
+
"R": {
|
|
139
|
+
"1": "██████╗ ",
|
|
140
|
+
"2": "██╔══██╗",
|
|
141
|
+
"3": "██████╔╝",
|
|
142
|
+
"4": "██╔══██╗",
|
|
143
|
+
"5": "██║ ██║",
|
|
144
|
+
"6": "╚═╝ ╚═╝"
|
|
145
|
+
},
|
|
146
|
+
"S": {
|
|
147
|
+
"1": "███████╗",
|
|
148
|
+
"2": "██╔════╝",
|
|
149
|
+
"3": "███████╗",
|
|
150
|
+
"4": "╚════██║",
|
|
151
|
+
"5": "███████║",
|
|
152
|
+
"6": "╚══════╝"
|
|
153
|
+
},
|
|
154
|
+
"T": {
|
|
155
|
+
"1": "████████╗",
|
|
156
|
+
"2": "╚══██╔══╝",
|
|
157
|
+
"3": " ██║ ",
|
|
158
|
+
"4": " ██║ ",
|
|
159
|
+
"5": " ██║ ",
|
|
160
|
+
"6": " ╚═╝ "
|
|
161
|
+
},
|
|
162
|
+
"U": {
|
|
163
|
+
"1": "██╗ ██╗",
|
|
164
|
+
"2": "██║ ██║",
|
|
165
|
+
"3": "██║ ██║",
|
|
166
|
+
"4": "██║ ██║",
|
|
167
|
+
"5": "╚██████╔╝",
|
|
168
|
+
"6": " ╚═════╝ "
|
|
169
|
+
},
|
|
170
|
+
"V": {
|
|
171
|
+
"1": "██╗ ██╗",
|
|
172
|
+
"2": "██║ ██║",
|
|
173
|
+
"3": "██║ ██║",
|
|
174
|
+
"4": "╚██╗ ██╔╝",
|
|
175
|
+
"5": " ╚████╔╝ ",
|
|
176
|
+
"6": " ╚═══╝ "
|
|
177
|
+
},
|
|
178
|
+
"W": {
|
|
179
|
+
"1": "██╗ ██╗",
|
|
180
|
+
"2": "██║ ██║",
|
|
181
|
+
"3": "██║ █╗ ██║",
|
|
182
|
+
"4": "██║███╗██║",
|
|
183
|
+
"5": "╚███╔███╔╝",
|
|
184
|
+
"6": " ╚══╝╚══╝ "
|
|
185
|
+
},
|
|
186
|
+
"X": {
|
|
187
|
+
"1": "██╗ ██╗",
|
|
188
|
+
"2": "╚██╗██╔╝",
|
|
189
|
+
"3": " ╚███╔╝ ",
|
|
190
|
+
"4": " ██╔██╗ ",
|
|
191
|
+
"5": "██╔╝ ██╗",
|
|
192
|
+
"6": "╚═╝ ╚═╝"
|
|
193
|
+
},
|
|
194
|
+
"Y": {
|
|
195
|
+
"1": "██╗ ██╗",
|
|
196
|
+
"2": "╚██╗ ██╔╝",
|
|
197
|
+
"3": " ╚████╔╝ ",
|
|
198
|
+
"4": " ╚██╔╝ ",
|
|
199
|
+
"5": " ██║ ",
|
|
200
|
+
"6": " ╚═╝ "
|
|
201
|
+
},
|
|
202
|
+
"Z": {
|
|
203
|
+
"1": "███████╗",
|
|
204
|
+
"2": "╚══███╔╝",
|
|
205
|
+
"3": " ███╔╝ ",
|
|
206
|
+
"4": " ███╔╝ ",
|
|
207
|
+
"5": "███████╗",
|
|
208
|
+
"6": "╚══════╝"
|
|
209
|
+
},
|
|
210
|
+
" ": {
|
|
211
|
+
"1": " ",
|
|
212
|
+
"2": " ",
|
|
213
|
+
"3": " ",
|
|
214
|
+
"4": " ",
|
|
215
|
+
"5": " ",
|
|
216
|
+
"6": " "
|
|
217
|
+
}
|
|
218
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
{
|
|
2
|
+
"A": {
|
|
3
|
+
"1": "┏━┓",
|
|
4
|
+
"2": "┣━┫",
|
|
5
|
+
"3": "┛ ┗"
|
|
6
|
+
},
|
|
7
|
+
"B": {
|
|
8
|
+
"1": "┳━┓",
|
|
9
|
+
"2": "┣━┫",
|
|
10
|
+
"3": "┻━┛"
|
|
11
|
+
},
|
|
12
|
+
"C": {
|
|
13
|
+
"1": "┏━┓",
|
|
14
|
+
"2": "┃ ",
|
|
15
|
+
"3": "┗━┛"
|
|
16
|
+
},
|
|
17
|
+
"D": {
|
|
18
|
+
"1": "┳━┓",
|
|
19
|
+
"2": "┃ ┃",
|
|
20
|
+
"3": "┻━┛"
|
|
21
|
+
},
|
|
22
|
+
"E": {
|
|
23
|
+
"1": "┏━┓",
|
|
24
|
+
"2": "┣━ ",
|
|
25
|
+
"3": "┗━┛"
|
|
26
|
+
},
|
|
27
|
+
"F": {
|
|
28
|
+
"1": "┏━┓",
|
|
29
|
+
"2": "┣━ ",
|
|
30
|
+
"3": "┻ "
|
|
31
|
+
},
|
|
32
|
+
"G": {
|
|
33
|
+
"1": "┏━┓",
|
|
34
|
+
"2": "┃┏┓",
|
|
35
|
+
"3": "┗━┛"
|
|
36
|
+
},
|
|
37
|
+
"H": {
|
|
38
|
+
"1": "┓ ┏",
|
|
39
|
+
"2": "┣━┫",
|
|
40
|
+
"3": "┛ ┗"
|
|
41
|
+
},
|
|
42
|
+
"I": {
|
|
43
|
+
"1": " ┳ ",
|
|
44
|
+
"2": " ┃ ",
|
|
45
|
+
"3": " ┻ "
|
|
46
|
+
},
|
|
47
|
+
"J": {
|
|
48
|
+
"1": " ┏┳",
|
|
49
|
+
"2": " ┃",
|
|
50
|
+
"3": "┗━┛"
|
|
51
|
+
},
|
|
52
|
+
"K": {
|
|
53
|
+
"1": "┓┏┓",
|
|
54
|
+
"2": "┣┫ ",
|
|
55
|
+
"3": "┛┗┛"
|
|
56
|
+
},
|
|
57
|
+
"L": {
|
|
58
|
+
"1": "┓ ",
|
|
59
|
+
"2": "┃ ",
|
|
60
|
+
"3": "┗━┛"
|
|
61
|
+
},
|
|
62
|
+
"M": {
|
|
63
|
+
"1": "┳┓┓",
|
|
64
|
+
"2": "┃┃┃",
|
|
65
|
+
"3": "┛ ┗"
|
|
66
|
+
},
|
|
67
|
+
"N": {
|
|
68
|
+
"1": "┳━┓",
|
|
69
|
+
"2": "┃ ┃",
|
|
70
|
+
"3": "┛ ┗"
|
|
71
|
+
},
|
|
72
|
+
"O": {
|
|
73
|
+
"1": "┏━┓",
|
|
74
|
+
"2": "┃ ┃",
|
|
75
|
+
"3": "┗━┛"
|
|
76
|
+
},
|
|
77
|
+
"P": {
|
|
78
|
+
"1": "┳━┓",
|
|
79
|
+
"2": "┣━┛",
|
|
80
|
+
"3": "┻ "
|
|
81
|
+
},
|
|
82
|
+
"Q": {
|
|
83
|
+
"1": "┏━┓",
|
|
84
|
+
"2": "┃ ┃",
|
|
85
|
+
"3": "┗━┻"
|
|
86
|
+
},
|
|
87
|
+
"R": {
|
|
88
|
+
"1": "┳━┓",
|
|
89
|
+
"2": "┣┳┛",
|
|
90
|
+
"3": "┛┗┛"
|
|
91
|
+
},
|
|
92
|
+
"S": {
|
|
93
|
+
"1": "┏━┓",
|
|
94
|
+
"2": "┗━┓",
|
|
95
|
+
"3": "┗━┛"
|
|
96
|
+
},
|
|
97
|
+
"T": {
|
|
98
|
+
"1": "┏┳┓",
|
|
99
|
+
"2": " ┃ ",
|
|
100
|
+
"3": " ┻ "
|
|
101
|
+
},
|
|
102
|
+
"U": {
|
|
103
|
+
"1": "┳ ┳",
|
|
104
|
+
"2": "┃ ┃",
|
|
105
|
+
"3": "┗━┛"
|
|
106
|
+
},
|
|
107
|
+
"V": {
|
|
108
|
+
"1": "┓ ┏",
|
|
109
|
+
"2": "┃┏┛",
|
|
110
|
+
"3": "┗┛ "
|
|
111
|
+
},
|
|
112
|
+
"W": {
|
|
113
|
+
"1": "┓ ┏",
|
|
114
|
+
"2": "┃┃┃",
|
|
115
|
+
"3": "┗┻┛"
|
|
116
|
+
},
|
|
117
|
+
"X": {
|
|
118
|
+
"1": "┏┓┏━",
|
|
119
|
+
"2": " ┣┫ ",
|
|
120
|
+
"3": "━┛┗┛"
|
|
121
|
+
},
|
|
122
|
+
"Y": {
|
|
123
|
+
"1": "┓ ┏",
|
|
124
|
+
"2": "┗━┫",
|
|
125
|
+
"3": "┗━┛"
|
|
126
|
+
},
|
|
127
|
+
"Z": {
|
|
128
|
+
"1": "━━┓",
|
|
129
|
+
"2": "┏┛ ",
|
|
130
|
+
"3": "┗━┛"
|
|
131
|
+
},
|
|
132
|
+
" ": {
|
|
133
|
+
"1": " ",
|
|
134
|
+
"2": " ",
|
|
135
|
+
"3": " "
|
|
136
|
+
}
|
|
137
|
+
}
|
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
"""
|
|
2
|
+
dedicated tools to create interactive UI in command line window
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import tkinter
|
|
6
|
+
import tkinter.filedialog
|
|
7
|
+
import subprocess
|
|
8
|
+
import os
|
|
9
|
+
import sys
|
|
10
|
+
import keyboard
|
|
11
|
+
from .ascii_art_font import ascii_art_font as ascii_art
|
|
12
|
+
|
|
13
|
+
#----------------------------------------------------------------------------------------------------------------------------------
|
|
14
|
+
class colored_output:
|
|
15
|
+
"""
|
|
16
|
+
Colors: black, red, green, yellow, blue, magenta, cyan, white.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
_NORMAL_FG = {
|
|
20
|
+
'BLACK': 30, 'RED': 31, 'GREEN': 32, 'YELLOW': 33,
|
|
21
|
+
'BLUE': 34, 'MAGENTA': 35, 'CYAN': 36, 'WHITE': 37
|
|
22
|
+
}
|
|
23
|
+
_NORMAL_BG = {
|
|
24
|
+
'BLACK': 40, 'RED': 41, 'GREEN': 42, 'YELLOW': 43,
|
|
25
|
+
'BLUE': 44, 'MAGENTA': 45, 'CYAN': 46, 'WHITE': 47
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
_BRIGHT_FG = {
|
|
29
|
+
'BLACK': 90, 'RED': 91, 'GREEN': 92, 'YELLOW': 93,
|
|
30
|
+
'BLUE': 94, 'MAGENTA': 95, 'CYAN': 96, 'WHITE': 97
|
|
31
|
+
}
|
|
32
|
+
_BRIGHT_BG = {
|
|
33
|
+
'BLACK': 100, 'RED': 101, 'GREEN': 102, 'YELLOW': 103,
|
|
34
|
+
'BLUE': 104, 'MAGENTA': 105, 'CYAN': 106, 'WHITE': 107
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
def __init__(self, bright=False):
|
|
38
|
+
"""
|
|
39
|
+
:param bright: boolean, True = bright text
|
|
40
|
+
"""
|
|
41
|
+
self._bright = bright
|
|
42
|
+
if bright:
|
|
43
|
+
self._fg_map = self._BRIGHT_FG
|
|
44
|
+
self._bg_map = self._BRIGHT_BG
|
|
45
|
+
else:
|
|
46
|
+
self._fg_map = self._NORMAL_FG
|
|
47
|
+
self._bg_map = self._NORMAL_BG
|
|
48
|
+
|
|
49
|
+
self._RESET = "\033[0m"
|
|
50
|
+
|
|
51
|
+
def _get_escape(self, fg_color=None, bg_color=None):
|
|
52
|
+
"""
|
|
53
|
+
return ANSI color code
|
|
54
|
+
"""
|
|
55
|
+
codes = []
|
|
56
|
+
if fg_color and fg_color.upper() in self._fg_map:
|
|
57
|
+
codes.append(str(self._fg_map[fg_color.upper()]))
|
|
58
|
+
if bg_color and bg_color.upper() in self._bg_map:
|
|
59
|
+
codes.append(str(self._bg_map[bg_color.upper()]))
|
|
60
|
+
|
|
61
|
+
if not codes: return ""
|
|
62
|
+
return f"\033[{';'.join(codes)}m"
|
|
63
|
+
|
|
64
|
+
def print(self, *objects, color="white", background=None, sep=' ', end='\n', file=None, flush=False):
|
|
65
|
+
"""
|
|
66
|
+
print out colored content
|
|
67
|
+
|
|
68
|
+
:param color: string, text color
|
|
69
|
+
:param objects: any, printing objects
|
|
70
|
+
:param background: string, background color
|
|
71
|
+
:param sep: string, delimintor between printing objects
|
|
72
|
+
:param end: string, ending style
|
|
73
|
+
:param file: any, output stream
|
|
74
|
+
:param flush: boolean, flush buffer zone
|
|
75
|
+
"""
|
|
76
|
+
text = sep.join(str(obj) for obj in objects)
|
|
77
|
+
colored_text = f"{self._get_escape(color, background)}{text}{self._RESET}"
|
|
78
|
+
print(colored_text, end=end, file=file, flush=flush)
|
|
79
|
+
|
|
80
|
+
def get_print_string_text(self, *objects, sep=' ', color=None, background=None):
|
|
81
|
+
"""
|
|
82
|
+
return colored text string for print
|
|
83
|
+
|
|
84
|
+
:param color: string, text color
|
|
85
|
+
:param objects: any, printing objects
|
|
86
|
+
:param background: string, background color
|
|
87
|
+
:param sep: string, delimintor between printing objects
|
|
88
|
+
"""
|
|
89
|
+
text = sep.join(str(obj) for obj in objects)
|
|
90
|
+
escape = self._get_escape(color, background)
|
|
91
|
+
return f"{escape}{text}{self._RESET}"
|
|
92
|
+
|
|
93
|
+
# shared instance used for every output of this module
|
|
94
|
+
default_colored_output = colored_output(bright=False)
|
|
95
|
+
|
|
96
|
+
#----------------------------------------------------------------------------------------------------------------------------------
|
|
97
|
+
def display_width(text):
|
|
98
|
+
"""
|
|
99
|
+
get consistent displayed width for latin + chinese characters string
|
|
100
|
+
|
|
101
|
+
:param text: string, the text for getting length of display
|
|
102
|
+
"""
|
|
103
|
+
width = 0
|
|
104
|
+
for ch in str(text):
|
|
105
|
+
if '\u4e00' <= ch <= '\u9fff' or '\u3000' <= ch <= '\u303f' or '\uff00' <= ch <= '\uffef':
|
|
106
|
+
width += 2
|
|
107
|
+
else:
|
|
108
|
+
width += 1
|
|
109
|
+
return width
|
|
110
|
+
|
|
111
|
+
#----------------------------------------------------------------------------------------------------------------------------------
|
|
112
|
+
def safe_input(prompt=""):
|
|
113
|
+
"""
|
|
114
|
+
input with preventions of previous input buffer overflow
|
|
115
|
+
|
|
116
|
+
:param prompt: string, text displayed before asking for input
|
|
117
|
+
"""
|
|
118
|
+
if prompt: default_colored_output.print(prompt, color='white', end='', flush=True)
|
|
119
|
+
|
|
120
|
+
result = []
|
|
121
|
+
|
|
122
|
+
while True:
|
|
123
|
+
try:
|
|
124
|
+
event = keyboard.read_event(suppress=True)
|
|
125
|
+
|
|
126
|
+
if event.event_type == keyboard.KEY_DOWN:
|
|
127
|
+
if event.name == 'enter':
|
|
128
|
+
print()
|
|
129
|
+
return ''.join(result)
|
|
130
|
+
elif event.name == 'backspace':
|
|
131
|
+
if result:
|
|
132
|
+
result.pop()
|
|
133
|
+
print('\b \b', end='', flush=True)
|
|
134
|
+
elif event.name == 'space':
|
|
135
|
+
result.append(' ')
|
|
136
|
+
default_colored_output.print(' ', color='white', end='', flush=True)
|
|
137
|
+
elif len(event.name) == 1:
|
|
138
|
+
result.append(event.name)
|
|
139
|
+
default_colored_output.print(event.name, color='white', end='', flush=True)
|
|
140
|
+
except:
|
|
141
|
+
return input()
|
|
142
|
+
|
|
143
|
+
#----------------------------------------------------------------------------------------------------------------------------------
|
|
144
|
+
|
|
145
|
+
def clear_screen():
|
|
146
|
+
"""
|
|
147
|
+
flush content on the console
|
|
148
|
+
"""
|
|
149
|
+
command = 'cls' if os.name == 'nt' else 'clear'
|
|
150
|
+
subprocess.run(command, shell=True, check=False)
|
|
151
|
+
|
|
152
|
+
#----------------------------------------------------------------------------------------------------------------------------------
|
|
153
|
+
|
|
154
|
+
def print_banner(input_string, color='white'):
|
|
155
|
+
"""
|
|
156
|
+
print a text rendered in the banner ascii-art style.
|
|
157
|
+
|
|
158
|
+
:param input_string: string, the text to render, '\\n' splits it into rows
|
|
159
|
+
:param color: string, text color (default: white)
|
|
160
|
+
"""
|
|
161
|
+
default_colored_output.print(ascii_art.default_banner(input_string), color=color, background=None)
|
|
162
|
+
|
|
163
|
+
def print_header(input_string, color='white'):
|
|
164
|
+
"""
|
|
165
|
+
print a text rendered in the header ascii-art style.
|
|
166
|
+
|
|
167
|
+
:param input_string: string, the text to render, '\\n' splits it into rows
|
|
168
|
+
:param color: string, text color (default: white)
|
|
169
|
+
"""
|
|
170
|
+
|
|
171
|
+
default_colored_output.print(ascii_art.default_header(input_string), color=color, background=None)
|
|
172
|
+
|
|
173
|
+
#----------------------------------------------------------------------------------------------------------------------------------
|
|
174
|
+
|
|
175
|
+
def select_files(file_types):
|
|
176
|
+
"""
|
|
177
|
+
opens file selection dialog.
|
|
178
|
+
|
|
179
|
+
:param file_types: [string...], list of wanted types, eg. ['jpg', 'pdf', 'txt]
|
|
180
|
+
:param
|
|
181
|
+
|
|
182
|
+
:return: the selected file location, if not selected return None
|
|
183
|
+
"""
|
|
184
|
+
root = tkinter.Tk()
|
|
185
|
+
root.withdraw()
|
|
186
|
+
|
|
187
|
+
print()
|
|
188
|
+
|
|
189
|
+
if isinstance(file_types, str):
|
|
190
|
+
file_types = [file_types]
|
|
191
|
+
|
|
192
|
+
filetypes = []
|
|
193
|
+
for ft in file_types:
|
|
194
|
+
ext = ft.lstrip('*.')
|
|
195
|
+
desc = ext.lower() + ' File'
|
|
196
|
+
filetypes.append((desc, ft))
|
|
197
|
+
filetypes.append(('All Files', '*.*'))
|
|
198
|
+
|
|
199
|
+
files = tkinter.filedialog.askopenfilenames(
|
|
200
|
+
title='File selection',
|
|
201
|
+
filetypes=filetypes
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
root.destroy()
|
|
205
|
+
return files if files else None
|
|
206
|
+
|
|
207
|
+
#----------------------------------------------------------------------------------------------------------------------------------
|
|
208
|
+
|
|
209
|
+
def select_folder():
|
|
210
|
+
"""
|
|
211
|
+
opens file selection dialog.
|
|
212
|
+
|
|
213
|
+
:return: the selected folder path, if not selected return None
|
|
214
|
+
"""
|
|
215
|
+
root = tkinter.Tk()
|
|
216
|
+
root.withdraw()
|
|
217
|
+
folder = tkinter.filedialog.askdirectory(title='Folder selection')
|
|
218
|
+
root.destroy()
|
|
219
|
+
return folder if folder else None;
|
|
220
|
+
|
|
221
|
+
#----------------------------------------------------------------------------------------------------------------------------------
|
|
222
|
+
|
|
223
|
+
def print_yesorno(prompt):
|
|
224
|
+
"""
|
|
225
|
+
print yes or no choice to let user select.
|
|
226
|
+
|
|
227
|
+
:param prompt: string/func, the prompt displayed before asking.
|
|
228
|
+
|
|
229
|
+
:return: string, 'y' or 'n'
|
|
230
|
+
"""
|
|
231
|
+
options = [
|
|
232
|
+
{
|
|
233
|
+
'text': 'Yes',
|
|
234
|
+
'color': 'green',
|
|
235
|
+
'id': 'y'
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
'text': 'No',
|
|
239
|
+
'color': 'red',
|
|
240
|
+
'id': 'n'
|
|
241
|
+
}
|
|
242
|
+
]
|
|
243
|
+
return print_selections(prompt, options)
|
|
244
|
+
|
|
245
|
+
def print_selections(prompt, options):
|
|
246
|
+
|
|
247
|
+
"""
|
|
248
|
+
print menu options could be operated by up and down arrow to select. returns the id of the option.
|
|
249
|
+
|
|
250
|
+
:param prompt" string/func, the prompt string or prompt builder function(need to return string)
|
|
251
|
+
:param options: dict, the options for choose
|
|
252
|
+
{
|
|
253
|
+
text: string, the option text displayed
|
|
254
|
+
color: string, color of the option displayed(default: white)
|
|
255
|
+
id: string, unique identifier for each option
|
|
256
|
+
}
|
|
257
|
+
"""
|
|
258
|
+
def hide_cursor(): print('\033[?25l', end='', flush=True)
|
|
259
|
+
def show_cursor(): print('\033[?25h', end='', flush=True)
|
|
260
|
+
|
|
261
|
+
active_index = 0
|
|
262
|
+
|
|
263
|
+
# Calculate max length once for lining up entries
|
|
264
|
+
target_w = max(display_width(str(option['text'])) for option in options)
|
|
265
|
+
target_w = 10 if target_w < 10 else target_w
|
|
266
|
+
|
|
267
|
+
if not callable(prompt): default_colored_output.print(prompt, color='white')
|
|
268
|
+
else: default_colored_output.print(prompt(), color='white')
|
|
269
|
+
|
|
270
|
+
def print_options():
|
|
271
|
+
for idx, option in enumerate(options):
|
|
272
|
+
lead_cursor = ">·" if idx == active_index else " "
|
|
273
|
+
lag_cursor = "·<" if idx == active_index else " "
|
|
274
|
+
|
|
275
|
+
text = option['text']
|
|
276
|
+
color = option.get('color') or 'white'
|
|
277
|
+
|
|
278
|
+
current_w = display_width(text)
|
|
279
|
+
filler = ('·' if active_index == idx else " ") * (max((target_w - current_w), 0) + 4 - display_width(lead_cursor))
|
|
280
|
+
|
|
281
|
+
default_colored_output.print(' '*(4 - display_width(lead_cursor)), color='white', end='', flush=True)
|
|
282
|
+
default_colored_output.print(f"{lead_cursor}", color='white', end='', flush=True)
|
|
283
|
+
default_colored_output.print(f"{idx+1}.{text}", color=color, end='', flush=True)
|
|
284
|
+
default_colored_output.print(f"{filler}{lag_cursor}", color='white', flush=True)
|
|
285
|
+
|
|
286
|
+
hide_cursor()
|
|
287
|
+
# Print menu
|
|
288
|
+
print_options()
|
|
289
|
+
|
|
290
|
+
# Main loop for arrow key navigation
|
|
291
|
+
while True:
|
|
292
|
+
# Move cursor up to redraw menu
|
|
293
|
+
print(f"\033[{len(options) + 1}A", flush=True)
|
|
294
|
+
print_options()
|
|
295
|
+
|
|
296
|
+
# Get key press
|
|
297
|
+
key = keyboard.read_event(suppress=True)
|
|
298
|
+
|
|
299
|
+
if key.event_type == keyboard.KEY_DOWN:
|
|
300
|
+
if key.name == 'up':
|
|
301
|
+
active_index = (active_index - 1) % len(options)
|
|
302
|
+
elif key.name == 'down':
|
|
303
|
+
active_index = (active_index + 1) % len(options)
|
|
304
|
+
elif key.name == 'enter':
|
|
305
|
+
show_cursor()
|
|
306
|
+
return options[active_index]['id']
|
|
307
|
+
else: continue
|
|
308
|
+
|
|
309
|
+
def menu(name, prompt, options, home=False):
|
|
310
|
+
"""
|
|
311
|
+
open up a menu with single selection toward other menu
|
|
312
|
+
|
|
313
|
+
:param name: string, the name of the panel
|
|
314
|
+
:param prompt: string/func, the prompt pr a prompt builder function(need to return string)
|
|
315
|
+
:param options: [dict], the options informations
|
|
316
|
+
[
|
|
317
|
+
{
|
|
318
|
+
text: string,
|
|
319
|
+
color: string,
|
|
320
|
+
id: string,
|
|
321
|
+
func: {
|
|
322
|
+
body: function,
|
|
323
|
+
param: [string...],
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
...
|
|
327
|
+
]
|
|
328
|
+
"""
|
|
329
|
+
# gather the necessary information for print_selections(options)
|
|
330
|
+
menu_options = [{'text': option['text'], 'color': option.get('color') or None, 'id': option['id']} for option in options]
|
|
331
|
+
|
|
332
|
+
# add back option to allow to return to previous menu
|
|
333
|
+
if home: menu_options.append({'text': 'quit program', 'color': '', 'id': 'quit'})
|
|
334
|
+
else: menu_options.append({'text': 'return to previous', 'color': '', 'id': 'back'})
|
|
335
|
+
# check for any duplicated id to prevent ambiguation in searching the options
|
|
336
|
+
if len([d.get('id') for d in menu_options]) != len(set([d.get('id') for d in menu_options])):
|
|
337
|
+
raise Exception('duplicated id for options')
|
|
338
|
+
|
|
339
|
+
while True:
|
|
340
|
+
clear_screen()
|
|
341
|
+
if not home:
|
|
342
|
+
print_header(name)
|
|
343
|
+
else:
|
|
344
|
+
print_banner(name)
|
|
345
|
+
if not callable(prompt): default_colored_output.print(prompt, color='white')
|
|
346
|
+
else: default_colored_output.print(prompt(), color='white')
|
|
347
|
+
print()
|
|
348
|
+
|
|
349
|
+
chosen_id = print_selections(
|
|
350
|
+
f'Choose from the following {len(menu_options)} options:',
|
|
351
|
+
menu_options
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
if chosen_id in ['back', 'quit']:
|
|
355
|
+
return chosen_id
|
|
356
|
+
|
|
357
|
+
selected = None
|
|
358
|
+
for opt in options:
|
|
359
|
+
if opt['id'] == chosen_id:
|
|
360
|
+
selected = opt
|
|
361
|
+
break
|
|
362
|
+
|
|
363
|
+
if not selected: raise Exception(f'no defined action for an option: {selected}.')
|
|
364
|
+
|
|
365
|
+
# If there's a 'func', execute it
|
|
366
|
+
if 'func' in selected:
|
|
367
|
+
func = selected['func']
|
|
368
|
+
if callable(func):
|
|
369
|
+
result = func()
|
|
370
|
+
elif isinstance(func, dict) and 'body' in func:
|
|
371
|
+
body = func['body']
|
|
372
|
+
params = func.get('param', [])
|
|
373
|
+
result = body(*params)
|
|
374
|
+
else: raise Exception(f'action provided for an option: {selected} is not callable.')
|
|
375
|
+
if result == 'quit':
|
|
376
|
+
return result
|
|
377
|
+
|
|
378
|
+
def home_menu(name, prompt, options):
|
|
379
|
+
"""
|
|
380
|
+
open a root menu with single selection toward other menu
|
|
381
|
+
|
|
382
|
+
:param name: string, the name of the panel
|
|
383
|
+
:param prompt: string, the prompt
|
|
384
|
+
:param options: [dict...], the options informations
|
|
385
|
+
[
|
|
386
|
+
{
|
|
387
|
+
text: string,
|
|
388
|
+
color: string,
|
|
389
|
+
id: string,
|
|
390
|
+
func: {
|
|
391
|
+
body: function,
|
|
392
|
+
param: [string...],
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
...
|
|
396
|
+
]
|
|
397
|
+
"""
|
|
398
|
+
result = menu(name, prompt, options, home=True)
|
|
399
|
+
if result == 'quit': quit_program(0)
|
|
400
|
+
|
|
401
|
+
def quit_program(code: int = 0):
|
|
402
|
+
clear_screen()
|
|
403
|
+
default_colored_output.print("Exiting program.", color='white')
|
|
404
|
+
default_colored_output.print('='*30, color='white')
|
|
405
|
+
|
|
406
|
+
try: keyboard.unhook_all()
|
|
407
|
+
except: pass
|
|
408
|
+
finally: sys.exit(code)
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""
|
|
2
|
+
command line interface for prettier_console
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import zipfile
|
|
6
|
+
import hashlib
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
import argparse
|
|
9
|
+
import os
|
|
10
|
+
import sys
|
|
11
|
+
|
|
12
|
+
from .ascii_art_font import ascii_art_font
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _cmd_updatefont(args):
|
|
16
|
+
if args.banner: style = 'banner'
|
|
17
|
+
elif args.header: style = 'header'
|
|
18
|
+
else: style = args.style if args.style else os.path.splitext(os.path.basename(args.change_font))[0]
|
|
19
|
+
|
|
20
|
+
try:
|
|
21
|
+
font_folder_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'ascii_art_font', 'font')
|
|
22
|
+
target_path = os.path.join(font_folder_path, f'{style}.json')
|
|
23
|
+
|
|
24
|
+
if os.path.exists(target_path):
|
|
25
|
+
randID = hashlib.md5(f'{datetime.now().timestamp()}'.encode()).hexdigest()
|
|
26
|
+
zip_name = f'legacyfont-{style}-{randID}.zip'
|
|
27
|
+
os.makedirs(os.path.join(font_folder_path, "legacy_fonts"), exist_ok=True)
|
|
28
|
+
zip_path = os.path.join(font_folder_path, "legacy_fonts", zip_name)
|
|
29
|
+
|
|
30
|
+
with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
|
|
31
|
+
zipf.write(target_path, arcname=f'{style}.json')
|
|
32
|
+
|
|
33
|
+
ascii_art_font.set_cset(style, args.change_font)
|
|
34
|
+
print(f'Font updated.')
|
|
35
|
+
except Exception as e:
|
|
36
|
+
print(f'Error: {e}', file=sys.stderr)
|
|
37
|
+
sys.exit(1)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def main():
|
|
41
|
+
parser = argparse.ArgumentParser(prog='prettier_console')
|
|
42
|
+
subparsers = parser.add_subparsers(dest='command', required=True)
|
|
43
|
+
|
|
44
|
+
updatefont = subparsers.add_parser(
|
|
45
|
+
'updatefont',
|
|
46
|
+
help='add or replace an ascii-art font style from a text file'
|
|
47
|
+
)
|
|
48
|
+
updatefont.add_argument(
|
|
49
|
+
'change_font',
|
|
50
|
+
help="path to a text file with A-Z + space glyphs, one row per line, each character separated by '/'"
|
|
51
|
+
)
|
|
52
|
+
target = updatefont.add_mutually_exclusive_group()
|
|
53
|
+
target.add_argument('--banner', action='store_true', help='save as the banner style')
|
|
54
|
+
target.add_argument('--header', action='store_true', help='save as the header style')
|
|
55
|
+
target.add_argument('--style', help='style name to save as (default: the file name without extension)')
|
|
56
|
+
updatefont.set_defaults(func=_cmd_updatefont)
|
|
57
|
+
|
|
58
|
+
args = parser.parse_args()
|
|
59
|
+
args.func(args)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
if __name__ == '__main__':
|
|
63
|
+
main()
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: prettier_console
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Provide you a automation to construct a prettier interactive console in one call
|
|
5
|
+
Author-email: MrCosine <a95sun@uwaterloo.ca>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/mr-cosine/prettier_console
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Requires-Python: >=3.8
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
Requires-Dist: keyboard>=0.13.5
|
|
13
|
+
|
|
14
|
+
So yea some shortcuts for you to get a prettier interactive console. Um I think its good for lazy people. If you enjoy you enjoy and if you don't...you don't.
|
|
15
|
+
Will update this readme later on... I guess.
|
|
16
|
+
|
|
17
|
+
What you can do:
|
|
18
|
+
1. using interactive.menu() to create menus with single selections
|
|
19
|
+
2. Customize your page by inserting interactive parts using tools such as print_selections and print_yesorno.
|
|
20
|
+
3. draw ascii-art banners and headers
|
|
21
|
+
4. get colored outputs
|
|
22
|
+
|
|
23
|
+
dependencies:
|
|
24
|
+
build==1.5.0
|
|
25
|
+
colorama==0.4.6
|
|
26
|
+
keyboard==0.13.5
|
|
27
|
+
packaging==26.2
|
|
28
|
+
pyproject_hooks==1.2.0
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
prettier_console/__init__.py
|
|
4
|
+
prettier_console/interactive.py
|
|
5
|
+
prettier_console/manage.py
|
|
6
|
+
prettier_console.egg-info/PKG-INFO
|
|
7
|
+
prettier_console.egg-info/SOURCES.txt
|
|
8
|
+
prettier_console.egg-info/dependency_links.txt
|
|
9
|
+
prettier_console.egg-info/entry_points.txt
|
|
10
|
+
prettier_console.egg-info/requires.txt
|
|
11
|
+
prettier_console.egg-info/top_level.txt
|
|
12
|
+
prettier_console/ascii_art_font/__init__.py
|
|
13
|
+
prettier_console/ascii_art_font/ascii_art_font.py
|
|
14
|
+
prettier_console/ascii_art_font/font/banner.json
|
|
15
|
+
prettier_console/ascii_art_font/font/header.json
|
|
16
|
+
tests/ascii-art-test.py
|
|
17
|
+
tests/testcases.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
keyboard>=0.13.5
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "prettier_console"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Provide you a automation to construct a prettier interactive console in one call"
|
|
9
|
+
authors = [{name = "MrCosine", email = "a95sun@uwaterloo.ca"}]
|
|
10
|
+
license = {text = "MIT"}
|
|
11
|
+
readme = "README.md"
|
|
12
|
+
requires-python = ">=3.8"
|
|
13
|
+
dependencies = [
|
|
14
|
+
"keyboard>=0.13.5",
|
|
15
|
+
]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"License :: OSI Approved :: MIT License",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
[project.urls]
|
|
22
|
+
Homepage = "https://github.com/mr-cosine/prettier_console"
|
|
23
|
+
|
|
24
|
+
[project.scripts]
|
|
25
|
+
prettier_console = "prettier_console.manage:main"
|
|
26
|
+
|
|
27
|
+
[tool.setuptools.packages.find]
|
|
28
|
+
where = ["."]
|
|
29
|
+
|
|
30
|
+
[tool.setuptools.package-data]
|
|
31
|
+
prettier_console = ["ascii_art_font/font/*.json"]
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import prettier_console as pc
|
|
2
|
+
|
|
3
|
+
pc.print_banner('quick brown fox\njumps over the\nlazy dog', color="white")
|
|
4
|
+
pc.print_header('quick brown fox jumps over the lazy dog', color="white")
|
|
5
|
+
|
|
6
|
+
pc.print_banner('quick brown fox\njumps over the\nlazy dog', color="red")
|
|
7
|
+
pc.print_header('quick brown fox jumps over the lazy dog', color="blue")
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
from prettier_console import interactive
|
|
2
|
+
|
|
3
|
+
if __name__ == "__main__":
|
|
4
|
+
#==============================================================================================
|
|
5
|
+
def sub(*args):
|
|
6
|
+
def helloworld():
|
|
7
|
+
options = []
|
|
8
|
+
return interactive.menu(f'hello world', f"hello!", options)
|
|
9
|
+
#==========================================================================================
|
|
10
|
+
|
|
11
|
+
num = args
|
|
12
|
+
options = [
|
|
13
|
+
{
|
|
14
|
+
'text': "say hello",
|
|
15
|
+
'color': 'red',
|
|
16
|
+
'id': 'hello',
|
|
17
|
+
'func':
|
|
18
|
+
{
|
|
19
|
+
'body': helloworld
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
]
|
|
23
|
+
return interactive.menu('sub panel', f"this is panel {num}", options)
|
|
24
|
+
#==============================================================================================
|
|
25
|
+
|
|
26
|
+
welcome_text = 'welcome'
|
|
27
|
+
options = [
|
|
28
|
+
{
|
|
29
|
+
'text': "open panel 1",
|
|
30
|
+
'color': 'red',
|
|
31
|
+
'id': 'sub1',
|
|
32
|
+
'func':
|
|
33
|
+
{
|
|
34
|
+
'body': sub,
|
|
35
|
+
'param': [1] # positional argument
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
'text': "open panel 2",
|
|
40
|
+
'color': 'blue',
|
|
41
|
+
'id': 'sub2',
|
|
42
|
+
'func':
|
|
43
|
+
{
|
|
44
|
+
'body': sub,
|
|
45
|
+
'param': [2] # positional argument
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
]
|
|
49
|
+
interactive.home_menu('HOME', welcome_text, options)
|