-
Notifications
You must be signed in to change notification settings - Fork 8
/
Copy path_parseutil.py
184 lines (145 loc) · 5.45 KB
/
_parseutil.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
"""Utility file with custom argparse types / classes.
This utility file is used separate to avoid circular dependencies between
_parser.py and the individual _commands.py.
"""
from typing import Union
import argparse
import os
import re
class FileType:
"""Custom type for files with given extensions."""
def __init__(self, extensions: Union[tuple, list]):
self.extensions = extensions
def __call__(self, value): # noqa: D102
if not os.path.isfile(value):
raise argparse.ArgumentTypeError(f"Input must be a file. '{value}' is not.")
if not any([value.endswith(e) for e in self.extensions]):
raise argparse.ArgumentTypeError(
f"Input file must have extension {self.extensions}. '{value}' does not."
)
return value
class FileFolderType:
"""Custom type supporting folders or files with given extensions."""
def __init__(self, extensions: Union[tuple, list]):
self.extensions = extensions
def __call__(self, value): # noqa: D102
if not os.path.isdir(value):
if not FileType(self.extensions)(value):
raise argparse.ArgumentTypeError(
f"Input value must be file or folder. '{value}' is not."
)
return value
class FolderType:
"""Custom type supporting folders."""
def __init__(self):
pass
def __call__(self, value): # noqa: D102
if not os.path.isdir(value):
raise argparse.ArgumentTypeError(
f"Input value must be folder and must exist. '{value}' is not."
)
return value
class PixelSizeType:
"""Custom type for pixel size."""
def __init__(self):
pass
def __call__(self, value): # noqa: D102
# Attempt to parse as float
try:
return float(value)
except ValueError:
pass
# Attempt to parse as tuple
try:
value = tuple(map(float, value.split(",")))
if len(value) == 2:
return value
except ValueError:
pass
raise argparse.ArgumentTypeError(
f"Pixel size must be a float or a tuple of two floats. '{value}' is not."
)
class ShapeType:
"""Custom type for image shapes."""
def __init__(self):
self.remove_characters = ["(", ")", " "]
self.allowed_characters = "xy3ctz"
self.required_characters = ["x", "y"]
def __call__(self, value): # noqa: D102
if not isinstance(value, str):
raise ValueError(f"Input value must be a string. '{value}' is not.")
raw_value = value
for c in self.remove_characters:
value = value.replace(c, "")
if not bool(re.match(f"^[{self.allowed_characters},]+$", value)):
raise ValueError(
f"Input must only contain values '{self.allowed_characters},'. '{raw_value}' does not."
)
if not bool(
re.match(
f"^([{self.allowed_characters}],)+[{self.allowed_characters}]$", value
)
):
raise ValueError(
f"Input must have format '(?,?,?,?)'. '{raw_value}' does not."
)
if not all([c in value for c in self.required_characters]):
raise ValueError(
f"Input must contain {self.required_characters}. '{raw_value}' does not."
)
return raw_value
class ProbabilityType:
"""Custom type for probability (range 0-1)."""
def __init__(self):
pass
def __call__(self, value): # noqa: D102
try:
value = float(value)
except ValueError:
raise argparse.ArgumentTypeError(
f"Input value must be a float. '{value}' is not."
)
if value < 0.0 or value > 1.0:
raise argparse.ArgumentTypeError(
f"Input '{value}' not in range [0.0, 1.0]."
)
return value
class CustomFormatter(argparse.RawDescriptionHelpFormatter):
"""Custom changes to argparse's default help text formatter."""
def add_usage(self, usage, actions, groups, prefix=None):
"""Helpformatter internal usage description function overwrite."""
if prefix is None:
prefix = "Usage: "
return super(CustomFormatter, self).add_usage(usage, actions, groups, prefix)
class Color:
"""Addition of fancy colors in help text."""
ispos = os.name == "posix"
title = "\033[1m" if ispos else "" # bold
general = "\033[94m" if ispos else "" # blue
optional = "\033[92m" if ispos else "" # green
required = "\033[91m" if ispos else "" # red
end = "\033[0m" if ispos else ""
def _add_utils(parser: argparse.ArgumentParser):
"""A very hacky way of trying to move this group to the bottom of help text."""
group = parser.add_argument_group(f"{Color.general}General utilities{Color.end}")
group.add_argument(
"-h",
"--help",
action="help",
default=argparse.SUPPRESS,
help="Show this message.",
)
group.add_argument(
"-V",
"--version",
action="version",
version="%(prog)s 0.1.4",
help="Show %(prog)s's version number.",
)
group.add_argument(
"-v",
"--verbose",
action="store_true",
help="Set program output to verbose printing all important steps.",
)
group.add_argument("--debug", action="store_true", help=argparse.SUPPRESS)