-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathutils.py
219 lines (180 loc) · 6.16 KB
/
utils.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
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
import time
from collections import namedtuple
from os.path import basename, join
from shutil import copyfile
import clipboard
class Backup:
"""Backup file manager"""
def __init__(self, output_path, temp_dir):
self.output_path = output_path
self.backup_path = join(temp_dir, basename(output_path))
copyfile(output_path, self.backup_path)
def restore(self):
copyfile(self.backup_path, self.output_path)
# datetime for timestamps
def datetime_str():
return time.strftime("%d%m%y_%H%M%S")
def bracket_pad_num(num, maxnum):
"""Get a bracketed string representation of numbers, dealing with the maximum number padding"""
# compute padding
numpad = len(str(maxnum)) - len(str(num))
return "[{}]{}".format(num, " " * numpad)
def make_indexed_list(x_iter):
"""Produced an indexed list from the input collection"""
return ["{} {}".format(bracket_pad_num(i + 1, len(x_iter)), x_iter[i]) for i in range(len(x_iter))]
def get_file_contents(path):
"""Read file string contents"""
try:
with open(path) as f:
return f.read()
except IOError:
return None
def stringify(value, key, joiner=", "):
value = joiner.join(value).strip()
if value.endswith(","):
value = value[:-1]
return value.strip()
# convert objetct / members to list
def listify(x):
if type(x) not in (list, tuple):
x = [x]
if type(x[0]) not in (list, tuple):
x = [[k] for k in x]
return x
def get_single_index(inp):
""" get a single numeric from input"""
res = None
try:
res = int(inp)
except:
pass
return res
# paste handler
def paste(single_line=True):
pasted_content = clipboard.paste()
if single_line:
# remove newlines
pasted_content = pasted_content.replace("\n", " ")
return pasted_content
def limit_size(msg, max_size, trunc_symbol="..."):
"""Apply max-length truncation to a string message
"""
if len(msg) > max_size:
msg = msg[:max_size - len(trunc_symbol)] + trunc_symbol
return msg
# check if s equals or is the start of opts or any of its elements
def matches(partial, full):
if type(full) == list:
for c in full:
if matches(partial, c):
return True
return False
if not partial:
return False
return (partial == full or full.startswith(partial))
def to_namedtuple(conf_dict, ntname="xxx"):
keys = sorted(conf_dict.keys())
conf = namedtuple(ntname, keys)(*[conf_dict[k] for k in keys])
return conf
def string_is_index_list(inp: str):
"""Determine if the input has only slicable numeric list elements
"""
inp = inp.strip()
return len((inp)) > 0 and all([x in [" ", ":", "-"] or x.isdigit() for x in inp])
def is_valid_index_list(inp, reference_container=None):
if type(inp) is str:
if not string_is_index_list(inp):
return False
if ":" in inp:
consequtive_colons = any([inp[i] == inp[i + 1] == ":" for i in range(len(inp) - 1)])
if len(inp) == 1 or consequtive_colons:
return False
if reference_container is not None:
inp = get_index_list(inp, len(reference_container))
# check it's within limits
if any(i > len(reference_container) for i in inp):
return False
return True
def str_to_int(inp, default=None):
"""Cast string to integer"""
try:
return int(inp)
except ValueError:
return default
def handle_negative_index(idx, total):
"""Handle negative-valued indexes"""
if idx is not None and idx < 0:
idx = total + idx + 1
return idx
def parse_slice(num, total_index_num):
"""Parse a string representing pythonic index slicing"""
# allow pythonic slicing
try:
start, end = num.split(":")
except ValueError:
# only two-operand slices allowed
return None
start, end = str_to_int(start), str_to_int(end)
if start is None and end is None:
return None
start = handle_negative_index(start, total_index_num)
end = handle_negative_index(end, total_index_num)
slice_idxs = expand_slice(start, end, total_index_num)
return slice_idxs
def handle_string_index(num, total_index_num):
"""Function to map a string representation of indexes to list of ints"""
# cast to integer
try:
num = int(num)
# return a list to mantain uniform return type with potential slices
return [handle_negative_index(num, total_index_num)]
except ValueError:
# attempt to parse a slice
if ":" in num:
return parse_slice(num, total_index_num)
else:
# invalid string
return None
return None
def get_index_list(inp, total_index_num, allow_slicing=True):
"""Convert a string slicable numeric list to list of integers
"""
if type(inp) is str:
# split to string list
inp = inp.strip().split()
idxs = []
for i, num in enumerate(inp):
if type(num) is str:
# parse string representation of indexes / slice ranges
cur_idx = handle_string_index(num, total_index_num)
if cur_idx is None:
# non-parsable item encountered
return None
idxs.extend(cur_idx)
else:
# numeric
idxs.append(handle_negative_index(num, total_index_num))
return idxs
def expand_slice(start, end, total):
"""Generate sequence of idxs by slice operands"""
if end is None:
return list(range(start, total))
if start is None:
return list(range(0, end + 1))
if start == end:
return [start]
if start < end:
return list(range(start, end + 1))
if start > end:
return expand_slice(start, None, total) + expand_slice(0, end, total)
return None
def has_none(inp):
return inp is None or any([x is None for x in inp])
# debug with statement for visual
class OnlyDebug:
def __init__(self, visual):
self.visual = visual
def __enter__(self):
self.visual.set_only_debug(True)
def __exit__(self, exc_type, exc_val, exc_tb):
self.visual.set_only_debug(False)