Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 7
Expand file tree
/
Copy pathutils.py
More file actions
Latest commit
163 lines (126 loc) · 4.89 KB
/
Copy pathutils.py
File metadata and controls
163 lines (126 loc) · 4.89 KB
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
fromreimportmatch
importclick
fromsettingsimportCOURSE_NAME_PATTERN, COURSE_TYPES_TO_FLAGS
classValidationError(Exception):
def__init__(self, message: str, details: str):
super().__init__(message)
self.message=message
self.details=details
defparse_course_str(raw_class: str):
'''
This is the key parser for the class/course strings
String Format
-------------
Underlying assumptions about the format of the string (with the dept removed):
`F 01A. 01Z`
`F 1AHL 01`
First character - campus ('F' or 'D')
Next 4 characters - course name / ID with leading 0's and possibly a trailing '.'
Last characters - section string (1-3 chars)
NOTE: As of Fall 2020 data, all strings are between 7-8 chars,
and all section strings are between 2-3 chars.
Components
----------
Dept - Department
Course - Course name / ID (ex. '1AL')
Section - Class section (ex. '1HZ')
Flags - Extra flags from the section (ex. {'H', 'Z'})
:param raw_class: (str) The unparsed string, ex. `C S F001A01Z`
:return: (dict) the parsed data {'dept', 'course', 'section', 'flags'}
'''
# Split the raw course string by a space, to separate different parts
# ex. 'C S F001A01Z' => ['C', 'S', 'F001A01Z']
parts=raw_class.split(' ')
iflen(parts) <2:
raiseValidationError(
f"Raw course string ('{raw_class}') is invalid",
'At least two space separated parts could not be found'
)
# All parts excluding the last one are assumed to be part of the department name
# ex. `C S` => `CS`
dept=''.join(parts[0:-1])
# The last part is the actual course string (without the department)
# ex. `F001A01Z`
without_dept=parts[-1]
iflen(without_dept) <6orlen(without_dept) >8:
raiseValidationError(
f"Invalid course + section string ('{without_dept}')",
'Length is not between 6-8 chars'
)
# First five characters are the course name
# ex. `D001A` or `F04BH`
course=without_dept[0:5]
# Regex to clean the leading 'F'/'D' and extraneous 0's
match_obj=match(COURSE_NAME_PATTERN, course)
ifnotmatch_objornotmatch_obj.groups():
raiseValidationError(
f"Whoops, the course (ex. '24A') could not be extracted from '{course}'",
'The course name regex does not match'
)
iflen(match_obj[0]) <5:
raiseValidationError(
f"Whoops, the course (ex. '24A') could not be extracted from '{course}'",
'The course name regex does not fully match'
)
# Cleaned course name, e.g. `4A`
cleaned_course=match_obj[1]
# The last chars are the class section + flags
# ex. `01Z` or `5ZH`
section=without_dept[5:]
# Extract flags by filtering nonalphabets from the class section string
flags=set(filter(str.isalpha, section))
return {
'dept': dept,
'course': cleaned_course,
'section': section,
'flags': flags,
}
defget_class_type(campus: str, flags: set):
'''
From a given set of class flags, return the type of the class
:param campus: (str) The campus (check COURSE_TYPES_TO_FLAGS)
:param flags: (set) The flags for the class
:return: (str) The type of class
'''
class_type=None
forname, flaginCOURSE_TYPES_TO_FLAGS[campus].items():
# Exclude 'standard' because that is the fallback case
ifname!='standard'andflaginflags:
ifclass_type:
# TODO: should this be a warning instead?
raiseValidationError('Class has multiple types in its flags', '')
class_type=name
ifnotclass_type:
class_type='standard'
returnclass_type
deflog(tag, color, message, details=None, pad=True):
'''
Log a message to stdout
:param tag: (str) The tag, ex. `info` or `warn`
:param color: (str) The color for the tag (passed to colorama)
:param message: (str) The log message string
:param details: (dict) Extra details to show {'label': 'value'}
:param pad: (bool) Whether the extra detail lines should be padded
'''
formatted_tag=click.style(tag, fg=color, bold=True)
print(f'{formatted_tag}{message}')
ifdetails:
format_label=lambdatext: click.style(text, fg='white', dim=True, bold=True)
forlabel, msgindetails.items():
padding=len(tag) *' '+' 'ifpadelse''
print(padding+format_label(label), msg)
deflog_info(*args, **kwargs):
'''
Log an info message (see log())
'''
log('info', 'green', *args, **kwargs)
deflog_warn(*args, **kwargs):
'''
Log a warning (see log())
'''
log('warn', 'yellow', *args, **kwargs)
deflog_err(*args, **kwargs):
'''
Log an error (see log())
'''
log('err', 'red', *args, **kwargs)