Files
Class-Widgets/cses_mgr.py
2025-06-11 15:48:20 +08:00

272 lines
10 KiB
Python
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
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')