Files
Class-Widgets/cses_mgr.py

272 lines
10 KiB
Python
Raw Normal View History

2025-05-29 22:29:58 +08:00
"""
CSES Format Support
what is CSES: https://github.com/CSES-org/CSES
"""
import json
import typing
import cses
from datetime import datetime, timedelta
from loguru import logger
import list_ as list_
import conf
from file import base_directory, config_center
CSES_WEEKS_TEXTS = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
CSES_WEEKS = [1, 2, 3, 4, 5, 6, 7]
def _get_time(time: typing.Union[str, int]) -> datetime:
if isinstance(time, str):
return datetime.strptime(str(time), '%H:%M:%S')
elif isinstance(time, int):
return datetime.strptime(f'{int(time / 60 / 60)}:{int(time / 60 % 60)}:{time % 60}','%H:%M:%S')
else:
raise ValueError(f'需要 int 或 HH:MM:SS 类型,得到 {type(time)},值为 {time}')
class CSES_Converter:
"""
CSES 文件管理器
集成导入/导出CSES文件的功能
"""
def __init__(self, path='./'):
self.generator = None
self.parser = None
self.path = path
def load_parser(self):
if not cses.CSESParser.is_cses_file(self.path):
return "Error: Not a CSES file" # 判定格式
self.parser = cses.CSESParser(self.path)
return self.parser
def load_generator(self):
self.generator = cses.CSESGenerator(version=int(config_center.read_conf('Other', 'cses_version')))
def convert_to_cw(self):
"""
将CSES文件转换为Class Widgets格式
"""
try:
with open(f'{base_directory}/config/default.json', 'r', encoding='utf-8') as file: # 加载默认配置
cw_format = json.load(file)
except FileNotFoundError:
logger.error(f'File {base_directory}/config/default.json not found')
return False
if not self.parser:
raise Exception("Parser not loaded, please load_parser() first.")
# 课程表
cses_schedules = self.parser.get_schedules()
print(cses_schedules)
part_count = 0
part_list = []
for day in cses_schedules: # 课程
# name = day['name']
enable_day = day['enable_day']
weeks = day['weeks']
classes = day['classes']
last_end_time = None
class_count = 0
for class_ in classes: # 时间线
week = str(CSES_WEEKS.index(enable_day)) # 星期
subject = class_['subject'] # 课程名
time_diff = None
# 节点
if class_ == classes[0]:
raw_time = _get_time(class_['start_time'])
time = [raw_time.hour, raw_time.minute]
if time not in part_list: # 跳过重复的(已创建的)节点
cw_format['part'][str(part_count)] = time
cw_format['part_name'][str(part_count)] = f'Part {part_count}'
part_count += 1
part_list.append(time)
# 时间线
start_time = _get_time(class_['start_time'])
end_time = _get_time(class_['end_time'])
class_count += 1
# 计算时长
duration = int((end_time - start_time).total_seconds() / 60)
if last_end_time:
time_diff = int((start_time - last_end_time).total_seconds() / 60) # 时差
if not time_diff: # 如果连堂或第一节课
cw_format['timeline'][week][f'a{part_count - 1}{class_count}'] = duration
else:
cw_format['timeline'][week][f'f{part_count - 1}{class_count - 1}'] = time_diff
cw_format['timeline'][week][f'a{part_count - 1}{class_count}'] = duration
last_end_time = end_time
# 课程
if weeks == 'even':
cw_format['schedule_even'][week].append(subject)
elif weeks == 'odd':
cw_format['schedule'][week].append(subject)
elif weeks == 'all':
cw_format['schedule'][week].append(subject)
cw_format['schedule_even'][week].append(subject)
else:
logger.warning('本软件暂时不支持更多的周数循环')
print(cw_format)
return cw_format
def convert_to_cses(self, cw_data=None, cw_path='./'):
"""
将Class Widgets格式转换为CSES文件需提供保存路径和Class Widgets数据/路径
Args:
cw_data: Class Widgets格式数据 (Optional)
cw_path: Class Widgets文件路径(Optional)
"""
def convert(schedules, type_='odd'):
class_counter_dict = {} # 记录一个节点当天的课程数
for part in parts: # 节点循环
name = part_names[part]
part_start_time = datetime.strptime(f'{parts[part][0]}:{parts[part][1]}', '%H:%M')
print(f'Part {part}: {name} - {part_start_time.strftime("%H:%M")}')
class_counter_dict[part] = {}
for day, subjects in schedules.items():
time_counter = 0
class_counter = 0
if timelines[day]: # 自定时间线存在
timeline = timelines[day]
else: # 自定时间线不存在
timeline = timelines['default']
timelines_part = {str(day): []} # 一个节点的时间线列表
for key, time in timeline.items(): # 时间线循环
if key.startswith(f'a{part}'): # 科目
class_dict = {}
other_parts_classes = 0
for p, t in class_counter_dict.items(): # 超级嵌套
if p == part: # 排除当前节点
continue
all_time = 0
for c, d in t.items(): # 超级嵌套
if c != str(day): # 排除其他天
continue
all_time += d
other_parts_classes += all_time
start_time = part_start_time + timedelta(minutes=time_counter)
end_time = start_time + timedelta(minutes=int(time))
subject = subjects[int(key[2:]) - 1 + other_parts_classes]
class_counter += 1
if subject == '未添加': # 跳过未添加的科目
time_counter += int(time) # 时间叠加
continue
class_dict['subject'] = subject
class_dict['start_time'] = start_time.strftime('%H:%M:00')
class_dict['end_time'] = end_time.strftime('%H:%M:00')
timelines_part[str(day)].append(class_dict)
if key[1] == part: # 时间叠加counter
time_counter += int(time)
class_counter_dict[part][day] = class_counter # 记录一个节点当天的课程数
print(timelines_part)
if not timelines_part[str(day)]: # 跳过空时间线
continue
self.generator.add_schedule(
name=f'{name}_{CSES_WEEKS_TEXTS[int(day)]}',
enable_day=CSES_WEEKS[int(day)],
weeks=type_,
classes=[timelines_part[str(day)][i] for i in range(len(timelines_part[str(day)]))]
)
def check_subjects(schedule): # 检查课表是否有未正式设定的科目
unset_subjects = []
for _, classes in schedule.items():
for class_ in classes:
if class_ == '未添加':
continue
if class_ not in cw_subjects['subject_list']:
unset_subjects.append(class_)
return unset_subjects
"""
转换/CONVERT
"""
# 科目
try:
with open(f'{base_directory}/config/data/subject.json', 'r', encoding='utf-8') as data:
cw_subjects = json.load(data)
except FileNotFoundError:
logger.error(f'File {base_directory}/config/data/subject.json not found')
return False
for subject_ in cw_subjects['subject_list']:
self.generator.add_subject(
name=subject_, simplified_name=list_.get_subject_abbreviation(subject_),
teacher=None, room=None
)
# 课表
if not self.generator:
raise Exception("Generator not loaded, please load_generator() first.")
if cw_path != './' and cw_data is None: # 加载Class Widgets数据
try:
with open(cw_path, 'r', encoding='utf-8') as data:
cw_data = json.load(data)
except FileNotFoundError:
logger.error(f'File {cw_path} not found')
return False
else:
raise Exception("Please provide a path or a cw_data")
parts = cw_data['part']
part_names = cw_data['part_name']
timelines = cw_data['timeline']
schedules_odd = cw_data['schedule']
schedule_even = cw_data['schedule_even']
convert(schedules_odd)
convert(schedule_even, 'even')
us_set_odd = set(check_subjects(schedules_odd))
us_set_even = set(check_subjects(schedule_even))
us_union = us_set_odd.union(us_set_even)
for subject_ in list(us_union):
self.generator.add_subject(
name=subject_, simplified_name=list_.get_subject_abbreviation(subject_),
teacher=None, room=None
)
try:
self.generator.save_to_file(self.path)
return True
except Exception as e:
logger.error(f'Error: {e}')
return False
if __name__ == '__main__':
# EXAMPLE
importer = CSES_Converter(path='./config/cses_schedule/test.yaml')
importer.load_parser()
importer.convert_to_cw()
print('_____________________________', end='\n') # 输出分割线
exporter = CSES_Converter(path='./config/cses_schedule/test2.yaml')
exporter.load_generator()
exporter.convert_to_cses(cw_path='./config/schedule/default (3).json')