-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathhelpers.py
200 lines (141 loc) · 5.19 KB
/
helpers.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
from re import sub, match
import csv
from os import path
from typing import List, Tuple
from typing import Any
from constants import *
# ===== stdout =====
def bold(*msg, sep = ' ') -> str:
msg = sep.join(msg)
return f"\033[1m{msg}\033[0m"
def green(*msg, sep = ' ') -> str:
msg = sep.join(msg)
return f"\033[32m{msg}\033[0m"
def yellow(*msg, sep = ' ') -> str:
msg = sep.join(msg)
return f"\033[33m{msg}\033[0m"
def red(*msg) -> str:
msg = ' '.join(msg)
return f"\033[91m{msg}\033[0m"
def separator(length: int):
print('\u2500' * length)
def confirm(prompt: str) -> bool:
try:
response = input(prompt).strip().lower()
except KeyboardInterrupt:
print()
return False
except EOFError:
exit()
return response in ['yes', 'y']
# ===== Preprocessing =====
def standardize_notations(s: str) -> str:
s = sub(AND_PATTERNS, AND_OP, s)
# matching XOR before OR
# so that XOR doesn't get matched by OR
s = sub(XOR_PATTERNS, XOR_OP, s)
s = sub(OR_PATTERNS, OR_OP, s)
# matching IFF before IMPLY
# so that <-> doesn't get matched by ->
s = sub(IFF_PATTERNS, IFF_OP, s)
s = sub(IMPLIES_PATTERNS, IMPLIES_OP, s)
# match NOT last so that spaces are properly added
s = sub(NOT_PATTERNS, NOT_OP, s)
s = sub(r'\s+', ' ', s)
# handle cases: ( not ...
s = sub(r'\(\s+not', '(not', s)
return s
def parse(s: str) -> list:
# for each component: add quotes and separate by ,
s = sub(r'([^\s()]+\b|[^\w\s()]+\B)',
r'"\1",',
s)
# convert () to python list notation [[...],]
s = s.replace('(', '[')
s = sub(r'\)(?!$)', '],', s)
s = s.replace(')', ']') # last ) should not end in ,
struct = list(eval(s))
return struct
# ===== Miscellaneous =====
# NOTE: temporary solution to removing parentheses
def display(token) -> str:
# '(a V b)' -> 'a V b'
return sub(r'^\(([\s\S]*)\)$', r'\1', str(token))
# https://stackoverflow.com/questions/1653970/does-python-have-an-ordered-set
def unique(seq: List | Tuple) -> List:
return list(dict.fromkeys(seq))
# determine if a variable name conforms to the rules
def good_name(name: str) -> bool:
# 1. begins with a-z, A-Z or _ (underscore)
# 2. contains only a-z, A-Z, 0-9 or _
# 3. does not contain any spaces
pattern = r'^[a-zA-Z_]\w*$'
return bool(match(pattern, name))
# process new file name
def preprocess_filename(filepath: str) -> str:
filepath = filepath.strip()
if path.isdir(filepath):
filepath = path.join(filepath, 'output.csv')
# add extension if no extension is provided
if not filepath.endswith('.csv'):
filepath += '.csv'
# convert to absolute path, resolving ./../~/symlinks
# also expanding environment variable e.g. $HOME
filepath = path.realpath(path.expanduser(path.expandvars(filepath)))
# change file name if file path exists
counter = 1
while path.exists(filepath):
filepath = f"{filepath[:-4]}-{counter}.csv"
counter += 1
return filepath
# output a table via either stdout or csv
def output_table(table: List[List[Any]],
labels: str | None = None,
filepath: str | None = None):
if isinstance(labels, str):
# prepare custom labels
assert len(labels) == 2, CUSTOM_LABEL_LENGTH_ERROR
assert labels[0] != labels[1], CUSTOM_LABEL_IDENTICAL_ERROR
table = [
[labels[cell] if cell in [0, 1] else cell for cell in row]
for row in table
]
header = table[0]
if filepath:
# write to csv file
filepath = preprocess_filename(filepath)
if MARK_COLUMN in header:
# remove mark column
table = [row[:-1] for row in table]
with open(filepath, 'w') as csv_file:
writer = csv.writer(csv_file)
# write body
for row in table:
writer.writerow(row)
else:
col_widths: List[int] = []
# get lengthiest string per column as its width
for i in range(len(header)):
if header[i] == MARK_COLUMN:
col_widths.append(3)
else:
col_lengths = [len(str(row[i])) for row in table]
col_widths.append(max(col_lengths) + 2)
# create row templates
row_template, row_separator, border = [], [], []
for width in col_widths:
border.append(BOX_OUTER_HLINE * width)
row_template.append('{:^' + str(width) + '}')
row_separator.append(BOX_INNER_HLINE * width)
# create top/bottom borders
top_border = f"{BOX_TOP_LEFT}{BOX_TOP_T.join(border)}{BOX_TOP_RIGHT}"
bottom_border = f"{BOX_BOTTOM_LEFT}{BOX_BOTTOM_T.join(border)}{BOX_BOTTOM_RIGHT}"
# create row separator and separator
row_separator = f"\n{BOX_LEFT_T}{BOX_JOINT.join(row_separator)}{BOX_RIGHT_T}\n"
row_template = f"{BOX_OUTER_VLINE}{BOX_INNER_VLINE.join(row_template)}{BOX_OUTER_VLINE}"
rows = []
for row in table:
rows.append(row_template.format(*row))
print(top_border)
print(row_separator.join(rows))
print(bottom_border)