diff --git a/.gitignore b/.gitignore
index 15201ac..d86e4ff 100644
--- a/.gitignore
+++ b/.gitignore
@@ -165,7 +165,7 @@ cython_debug/
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
-#.idea/
+.idea/
# PyPI configuration file
.pypirc
diff --git a/.readthedocs.yaml b/.readthedocs.yaml
new file mode 100644
index 0000000..09aa424
--- /dev/null
+++ b/.readthedocs.yaml
@@ -0,0 +1,26 @@
+# Read the Docs configuration file
+# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
+
+# Required
+version: 2
+
+# Set the OS, Python version, and other tools you might need
+build:
+ os: ubuntu-24.04
+ tools:
+ python: "3.13"
+
+python:
+ install:
+ - requirements: ./requirements.txt
+
+# Build documentation in the "docs/" directory with Sphinx
+sphinx:
+ configuration: docs/source/conf.py
+
+# Optionally, but recommended,
+# declare the Python requirements required to build your documentation
+# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html
+# python:
+# install:
+# - requirements: docs/requirements.txt
diff --git a/Logo.png b/Logo.png
new file mode 100644
index 0000000..6086f32
Binary files /dev/null and b/Logo.png differ
diff --git a/README.md b/README.md
index 656a444..b70ce75 100644
--- a/README.md
+++ b/README.md
@@ -1,74 +1,13 @@
-
+
-
+# cseslib4py
-# PyCSES
+适用于 Python 的 CSES API
-CSES Access Framework for Python
-
-#### [Main Repo](https://github.com/SmartTeachCN/pycses)
-
-
-
-## Introduction
-
-PyCSES is a Python library that provides access to the CSES format. It is designed to be simple and easy to use.
-
-## Functions
-
-```python
-import cses
-
-# Read a CSES file
-parser = cses.CSESParser("path/to/file.cses.yaml")
-
-# Check if the file is valid
-if not cses.CSESParser.is_cses_file("path/to/file.cses.yaml"):
- print("Not a valid CSES file")
-
-# Get subjects
-for subject in parser.get_subjects():
- print("Name:", subject["name"])
- print("Simplified Name:", subject["simplified_name"])
- print("Teacher:", subject["teacher"])
- print("Room:", subject["room"])
- print("")
-
-# Get schedules
-for schedule in parser.get_schedules():
- print("Name:", schedule["name"])
- print("Enable Day:", schedule["enable_day"])
- print("Weeks:", schedule["weeks"])
- print("Classes:")
- for class_ in schedule["classes"]:
- print(" Subject:", class_["subject"])
- print(" Start Time:", class_["start_time"])
- print(" End Time:", class_["end_time"])
- print("")
-
-
-# Generate a CSES file
-generator = cses.CSESGenerator(version=1)
-
-# Add a subject
-generator.add_subject(name="Math", simplified_name="M", teacher="Mr. Wang", room="101")
-
-# Add a schedule
-generator.add_schedule(name="Monday", enable_day="mon", weeks=all, classes=[
- {
- "subject": "Math",
- "start_time": "08:00",
- "end_time": "09:00"
- },
- {
- "subject": "Biology",
- "start_time": "09:00",
- "end_time": "10:00"
- }
-])
-
-# Save the file
-generator.save_to_file("path/to/file.cses.yaml")
-```
+## 简介
+**cseslib4py** 是一个 Python 库,提供对 [CSES](https://github.com/SmartTeachCN/CSES) 格式的访问,同时具有简单易用的设计。
+此外,它由 [MacrosMeng](https://github.com/MacroMeng) 重构,以提供比已经停更的 [pycses](https://github.com/SmartTeachCN/pycses) **更好**和**具有更高易读性**的访问。
+## 许可证
+**cseslib4py** 采用 **MIT** 许可证。您可以在遵守许可证条款的前提下自由使用、修改和分发该库。
diff --git a/README.rst b/README.rst
deleted file mode 100644
index 5c7438d..0000000
--- a/README.rst
+++ /dev/null
@@ -1,79 +0,0 @@
-.. image:: https://static.smart-teach.cn/logos/full.jpg
- :height: 64
-
-PyCSES
-======
-
-一个用于解析与生成 CSES 格式的 Python 库,提供简单易用的 API。
-详细示例请见 [cses/__init__.py](cses/__init__.py)。
-
-安装
-----
-
-.. code-block:: console
-
- pip install pycses
-
-使用示例
---------
-
-.. code-block:: python
-
-import cses
-
-# Read a CSES file
-parser = cses.CSESParser("path/to/file.cses.yaml")
-
-# Check if the file is valid
-if not cses.CSESParser.is_cses_file("path/to/file.cses.yaml"):
- print("Not a valid CSES file")
-
-# Get subjects
-for subject in parser.get_subjects():
- print("Name:", subject["name"])
- print("Simplified Name:", subject["simplified_name"])
- print("Teacher:", subject["teacher"])
- print("Room:", subject["room"])
- print("")
-
-# Get schedules
-for schedule in parser.get_schedules():
- print("Name:", schedule["name"])
- print("Enable Day:", schedule["enable_day"])
- print("Weeks:", schedule["weeks"])
- print("Classes:")
- for class_ in schedule["classes"]:
- print(" Subject:", class_["subject"])
- print(" Start Time:", class_["start_time"])
- print(" End Time:", class_["end_time"])
- print("")
-
-
-# Generate a CSES file
-generator = cses.CSESGenerator(version=1)
-
-# Add a subject
-generator.add_subject(name="Math", simplified_name="M", teacher="Mr. Wang", room="101")
-
-# Add a schedule
-generator.add_schedule(name="Monday", enable_day="mon", weeks=all, classes=[
- {
- "subject": "Math",
- "start_time": "08:00",
- "end_time": "09:00"
- },
- {
- "subject": "Biology",
- "start_time": "09:00",
- "end_time": "10:00"
- }
-])
-
-# Save the file
-generator.save_to_file("path/to/file.cses.yaml")
- ...
-
-更多信息
---------
-
-请参阅 [README.md](README.md) 或 [LICENSE](LICENSE) 获取更多说明。
\ No newline at end of file
diff --git a/cses/__init__.py b/cses/__init__.py
index 9889277..8434628 100644
--- a/cses/__init__.py
+++ b/cses/__init__.py
@@ -1,224 +1,86 @@
+"""使用 ``CSES`` 类可以表示、解析一个 CSES 课程文件。"""
import yaml
-from pathlib import Path
-from collections import OrderedDict
-# 解析器
-class CSESParser:
- def __init__(self, file_path):
- """
- 初始化 CSES 解析器
-
- Args:
- file_path (str): CSES 格式的 YAML 文件路径
- """
- self.file_path = file_path
- self.data = None
- self.version = None
- self.subjects = []
- self.schedules = []
-
- self._load_file()
- self._parse_data()
-
- def _load_file(self):
- """加载并解析 YAML 文件"""
- try:
- with open(self.file_path, 'r', encoding='utf-8') as f:
- self.data = yaml.safe_load(f)
- except FileNotFoundError:
- raise FileNotFoundError(f"File {self.file_path} Not Found")
- except yaml.YAMLError as e:
- raise ValueError(f"YAML Error: {e}")
-
- def _parse_data(self):
- """解析 YAML 数据"""
- if not self.data:
- return
-
- # 获取版本信息
- self.version = self.data.get('version', 1)
-
- # 解析科目信息
- subjects_data = self.data.get('subjects', [])
- for subject in subjects_data:
- subject_info = {
- 'name': subject['name'],
- 'simplified_name': subject.get('simplified_name'),
- 'teacher': subject.get('teacher'),
- 'room': subject.get('room')
- }
- self.subjects.append(subject_info)
-
- # 解析课程安排
- schedules_data = self.data.get('schedules', [])
- for schedule in schedules_data:
- schedule_info = {
- 'name': schedule['name'],
- 'enable_day': schedule['enable_day'],
- 'weeks': schedule['weeks'],
- 'classes': []
- }
-
- # 解析课程
- classes_data = schedule.get('classes', [])
- for cls in classes_data:
- class_info = {
- 'subject': cls['subject'],
- 'start_time': cls['start_time'],
- 'end_time': cls['end_time']
- }
- schedule_info['classes'].append(class_info)
-
- self.schedules.append(schedule_info)
-
- def get_subjects(self):
- """获取所有科目信息"""
- return self.subjects
-
- def get_schedules(self):
- """获取所有课程安排"""
- return self.schedules
-
- def get_schedule_by_day(self, day):
+import cses.structures as st
+import cses.errors as err
+
+
+class CSES:
+ """
+ 用来表示、解析一个 CSES 课程文件的类。
+
+ 该类有如下属性:
+ - ``schedule``: 课程安排列表,每个元素是一个 ``SingleDaySchedule`` 对象。
+ - ``version``: 课程文件的版本号。目前只能为 ``1`` ,参见 CSES 官方文档与 Schema 文件。
+ - ``subjects``: 科目列表,每个元素是一个 ``Subject`` 对象。
+
+ Examples:
+ >>> c = CSES(open('../cses_example.yaml', encoding='utf8').read())
+ >>> c.version # 只会为 1
+ 1
+ >>> c.subjects # doctest: +NORMALIZE_WHITESPACE
+ {'数学': Subject(name='数学', simplified_name='数', teacher='李梅', room='101'),
+ '语文': Subject(name='语文', simplified_name='语', teacher='王芳', room='102'),
+ '英语': Subject(name='英语', simplified_name='英', teacher='张伟', room='103'),
+ '物理': Subject(name='物理', simplified_name='物', teacher='赵军', room='104')}
+
+ """
+
+ def __init__(self, content: str):
"""
- 根据星期获取课程安排
-
+ 初始化 CSES。
+
Args:
- day (str): 星期(如 'mon', 'tue' 等)
-
- Returns:
- list: 该星期的课程安排
+ content (str): CSES 课程文件的内容。
"""
- for schedule in self.schedules:
- if schedule['enable_day'] == day:
- return schedule['classes']
- return []
-
- @staticmethod
- def is_cses_file(file_path):
+ self.schedule = None
+ self.version = None
+ self.subjects = None
+
+ self._load(content)
+
+ def _load(self, content: str):
"""
- 判断是否为 CSES 格式的文件
-
+ 从 ``content`` 加载 CSES 课程文件的内容。
+
Args:
- file_path (str): 文件路径
-
- Returns:
- bool: 是否为 CSES 文件
+ content (str): CSES 课程文件的内容。
"""
+ data = yaml.safe_load(content)
+
+ # 版本处理&检查
+ self.version = data['version']
+ if self.version != 1:
+ raise err.VersionError(f'不支持的版本号: {self.version}')
+
+ # 科目处理&检查
try:
- with open(file_path, 'r', encoding='utf-8') as f:
- data = yaml.safe_load(f)
- return 'version' in data and 'subjects' in data and 'schedules' in data
- except:
- return False
-
-# 生成器
-class CSESGenerator:
- def __init__(self, version=1):
- """
- 初始化 CSES 生成器
-
- Args:
- version (int, optional): CSES 格式的版本号,默认为 1
- """
- self.version = version
- self.subjects = []
- self.schedules = []
+ self.subjects = {s['name']: st.Subject(**s) for s in data['subjects']}
+ except st.ValidationError as e:
+ raise err.ParseError(f'科目数据有误: {data['subjects']}') from e
- def add_subject(self, name, simplified_name=None, teacher=None, room=None):
- """
- 添加科目信息
-
- Args:
- name (str): 科目名称
- simplified_name (str, optional): 科目简称
- teacher (str, optional): 教师姓名
- room (str, optional): 教室名称
- """
- subject = {
- 'name': name,
- 'simplified_name': simplified_name,
- 'teacher': teacher,
- 'room': room
- }
- self.subjects.append(subject)
-
- def add_schedule(self, name, enable_day, weeks, classes):
- """
- 添加课程安排
-
- Args:
- name (str): 课程安排名称(如 "星期一")
- enable_day (str): 课程安排的星期(如 'mon', 'tue' 等)
- weeks (str): 周次类型(如 'all', 'odd', 'even')
- classes (list): 课程列表,每个课程包含以下键:
- - subject (str): 科目名称
- - start_time (str): 开始时间(如 '8:00')
- - end_time (str): 结束时间(如 '9:00')
- """
- schedule = {
- 'name': name,
- 'enable_day': enable_day,
- 'weeks': weeks,
- 'classes': classes
- }
- self.schedules.append(schedule)
-
- def generate_cses_data(self):
- """
- 生成 CSES 格式的字典数据
-
- Returns:
- dict: CSES 格式的字典数据
- """
- cses_data = {
- 'version': self.version,
- 'subjects': self.subjects,
- 'schedules': self.schedules
- }
- return cses_data
-
- def save_to_file(self, file_path):
- """
- 将 CSES 数据保存到 YAML 文件
-
- Args:
- file_path (str): 输出文件路径
- """
- cses_data = self.generate_cses_data()
-
+ # 课程处理&检查
+ schedules = data['schedules']
try:
- with open(file_path, 'w', encoding='utf-8') as f:
- yaml.dump(cses_data, f, default_flow_style=False, allow_unicode=True, sort_keys=False)
- except IOError as e:
- raise IOError(f"Failed to write {file_path}: {e}")
-
-# 示例用法
-if __name__ == "__main__":
- import sys
-
- if len(sys.argv) != 2:
- print("""Check CSES File
-Usage: python -m cses """)
- sys.exit(1)
-
- file_path = sys.argv[1]
-
- if not CSESParser.is_cses_file(file_path):
- print("Not a valid CSES file")
- sys.exit(1)
-
- parser = CSESParser(file_path)
-
- print("All Subjects:")
- for subject in parser.get_subjects():
- print(f"{subject['name']} ({subject.get('simplified_name', '')})")
- print(f"- Teacher: {subject.get('teacher', '')}")
- print(f"- Room: {subject.get('room', '')}")
-
- print("\nAll Schedules:")
- for schedule in parser.get_schedules():
- print(f"{schedule['name']} ({schedule['enable_day']} {schedule['weeks']}):")
- for cls in schedule['classes']:
- print(f"- {cls['subject']} ({cls['start_time']} - {cls['end_time']})")
+ # 先构造课程列表,再构造课表
+ schedule_classes = {i['name']: i['classes'] for i in schedules}
+ built_lessons = {i['name']: [] for i in schedules}
+ for name, classes in schedule_classes.items():
+ for lesson in classes:
+ built_lessons[name].append(
+ st.Lesson(**(lesson | {'subject': self.subjects[lesson['subject']]}))
+ ) # 从self.subjects中获取合法的Subject对象
+
+ # 从构造好的课程列表中构造课表
+ self.schedule = [
+ st.SingleDaySchedule(
+ enable_day=day['enable_day'],
+ classes=built_lessons[day['name']],
+ name=day['name'],
+ weeks=st.WeekType(day['weeks']),
+ )
+ for day in schedules
+ ]
+ except st.ValidationError as e:
+ raise err.ParseError(f'课程数据有误: {data['schedules']}') from e
diff --git a/cses/__init__.py.bak b/cses/__init__.py.bak
new file mode 100644
index 0000000..a951e2b
--- /dev/null
+++ b/cses/__init__.py.bak
@@ -0,0 +1,216 @@
+import yaml
+
+
+class CSESParser:
+ def __init__(self, fp):
+ """
+ 初始化 CSES 解析器。
+
+ Args:
+ fp (str): CSES 格式的 YAML 文件路径
+ """
+ self.file_path = fp
+ self.data = None
+ self.version = None
+ self.subjects = []
+ self.schedules = []
+
+ self._load_file()
+ self._parse_data()
+
+ def _load_file(self):
+ """加载并解析 YAML 文件"""
+ try:
+ with open(self.file_path, 'r', encoding='utf-8') as f:
+ self.data = yaml.safe_load(f)
+ print(self.data)
+ except FileNotFoundError:
+ raise FileNotFoundError(f"File {self.file_path} Not Found")
+ except yaml.YAMLError as e:
+ raise ValueError(f"YAML Error: {e}")
+
+ def _parse_data(self):
+ """解析 YAML 数据"""
+ if not self.data:
+ return
+
+ # 获取版本信息
+ self.version = self.data.get('version', 1)
+
+ # 解析科目信息
+ subjects_data = self.data.get('subjects', [])
+ for subject in subjects_data:
+ subject_info = {
+ 'name': subject['name'],
+ 'simplified_name': subject.get('simplified_name'),
+ 'teacher': subject.get('teacher'),
+ 'room': subject.get('room')
+ }
+ self.subjects.append(subject_info)
+
+ # 解析课程安排
+ schedules_data = self.data.get('schedules', [])
+ for schedule in schedules_data:
+ schedule_info = {
+ 'name': schedule['name'],
+ 'enable_day': schedule['enable_day'],
+ 'weeks': schedule['weeks'],
+ 'classes': []
+ }
+
+ # 解析课程
+ classes_data = schedule.get('classes', [])
+ for cls in classes_data:
+ class_info = {
+ 'subject': cls['subject'],
+ 'start_time': cls['start_time'],
+ 'end_time': cls['end_time']
+ }
+ schedule_info['classes'].append(class_info)
+
+ self.schedules.append(schedule_info)
+
+ def get_schedule_by_day(self, day):
+ """
+ 根据星期获取课程安排
+
+ Args:
+ day (str): 星期(如 'mon', 'tue' 等)
+
+ Returns:
+ list: 该星期的课程安排
+ """
+ for schedule in self.schedules:
+ if schedule['enable_day'] == day:
+ return schedule['classes']
+ return []
+
+ @staticmethod
+ def is_cses_file(file_path):
+ """
+ 判断是否为 CSES 格式的文件
+
+ Args:
+ file_path (str): 文件路径
+
+ Returns:
+ bool: 是否为 CSES 文件
+ """
+ try:
+ with open(file_path, 'r', encoding='utf-8') as f:
+ data = yaml.safe_load(f)
+ return 'version' in data and 'subjects' in data and 'schedules' in data
+ except:
+ return False
+
+# 生成器
+class CSESGenerator:
+ def __init__(self, version=1):
+ """
+ 初始化 CSES 生成器
+
+ Args:
+ version (int, optional): CSES 格式的版本号,默认为 1
+ """
+ self.version = version
+ self.subjects = []
+ self.schedules = []
+
+ def add_subject(self, name, simplified_name=None, teacher=None, room=None):
+ """
+ 添加科目信息
+
+ Args:
+ name (str): 科目名称
+ simplified_name (str, optional): 科目简称
+ teacher (str, optional): 教师姓名
+ room (str, optional): 教室名称
+ """
+ subject = {
+ 'name': name,
+ 'simplified_name': simplified_name,
+ 'teacher': teacher,
+ 'room': room
+ }
+ self.subjects.append(subject)
+
+ def add_schedule(self, name, enable_day, weeks, classes):
+ """
+ 添加课程安排
+
+ Args:
+ name (str): 课程安排名称(如 "星期一")
+ enable_day (str): 课程安排的星期(如 'mon', 'tue' 等)
+ weeks (str): 周次类型(如 'all', 'odd', 'even')
+ classes (list): 课程列表,每个课程包含以下键:
+ - subject (str): 科目名称
+ - start_time (str): 开始时间(如 '8:00')
+ - end_time (str): 结束时间(如 '9:00')
+ """
+ schedule = {
+ 'name': name,
+ 'enable_day': enable_day,
+ 'weeks': weeks,
+ 'classes': classes
+ }
+ self.schedules.append(schedule)
+
+ def generate_cses_data(self):
+ """
+ 生成 CSES 格式的字典数据
+
+ Returns:
+ dict: CSES 格式的字典数据
+ """
+ cses_data = {
+ 'version': self.version,
+ 'subjects': self.subjects,
+ 'schedules': self.schedules
+ }
+ return cses_data
+
+ def save_to_file(self, file_path):
+ """
+ 将 CSES 数据保存到 YAML 文件
+
+ Args:
+ file_path (str): 输出文件路径
+ """
+ cses_data = self.generate_cses_data()
+
+ try:
+ with open(file_path, 'w', encoding='utf-8') as f:
+ yaml.dump(cses_data, f, default_flow_style=False, allow_unicode=True, sort_keys=False)
+ except IOError as e:
+ raise IOError(f"Failed to write {file_path}: {e}")
+
+
+# 示例用法
+if __name__ == "__main__":
+ import sys
+
+ if len(sys.argv) != 2:
+ print("""Check CSES File
+Usage: python -m cses """)
+ sys.exit(1)
+
+ file_path = sys.argv[1]
+
+ if not CSESParser.is_cses_file(file_path):
+ print("Not a valid CSES file")
+ sys.exit(1)
+
+ parser = CSESParser(file_path)
+
+ print("All Subjects:")
+ for subject in parser.get_subjects():
+ print(f"{subject['name']} ({subject.get('simplified_name', '')})")
+ print(f"- Teacher: {subject.get('teacher', '')}")
+ print(f"- Room: {subject.get('room', '')}")
+
+ print("\nAll Schedules:")
+ for schedule in parser.get_schedules():
+ print(f"{schedule['name']} ({schedule['enable_day']} {schedule['weeks']}):")
+ for cls in schedule['classes']:
+ print(f"- {cls['subject']} ({cls['start_time']} - {cls['end_time']})")
+
diff --git a/cses/errors.py b/cses/errors.py
new file mode 100644
index 0000000..16e440b
--- /dev/null
+++ b/cses/errors.py
@@ -0,0 +1,14 @@
+"""``cseslib4py`` 所有的错误类型定义如下。"""
+
+
+class CSESError(Exception):
+ """所有由 ``cseslib4py`` 抛出的异常的基类。"""
+
+
+class ParseError(CSESError):
+ """解析 CSES 课程文件时抛出的异常。"""
+
+
+class VersionError(CSESError):
+ """解析 CSES 课程文件时,版本号错误抛出的异常。"""
+
diff --git a/cses/structures.py b/cses/structures.py
new file mode 100644
index 0000000..87f2cb9
--- /dev/null
+++ b/cses/structures.py
@@ -0,0 +1,186 @@
+"""
+本文档是cses包中的structures.py文件的文档。
+该文件定义了课程相关的数据结构,包括科目、课程、周次类型和单日课程安排。
+
+.. caution:: 该模块中的数据结构仅用于表示课程结构(与其附属工具),不包含实际的读取/写入功能。
+"""
+import datetime
+from enum import Enum
+from collections import UserList
+from collections.abc import Sequence
+from typing import override
+
+from pydantic import BaseModel, ValidationError
+
+import cses.utils as utils
+
+
+class Subject(BaseModel):
+ """
+ 单节课程科目。
+
+ Args:
+ name (str): 科目名称,如“语文”
+ simplified_name (str): 科目简化名称,如“语”
+ teacher (str): 教师姓名
+ room (str): 教室名称
+
+ Examples:
+ >>> s = Subject(name='语文', simplified_name='语', teacher='张三', room='A101')
+ >>> s.name
+ '语文'
+ >>> s.simplified_name
+ '语'
+ >>> s.teacher
+ '张三'
+ >>> s.room
+ 'A101'
+ """
+ name: str
+ simplified_name: str = ""
+ teacher: str = ""
+ room: str = ""
+
+
+class Lesson(BaseModel):
+ """
+ 单节课程。
+
+ Args:
+ subject (Subject): 课程的科目
+ start_time (datetime.time): 开始的时间
+ end_time (datetime.time): 结束的时间
+
+ Examples:
+ >>> l = Lesson(subject=Subject(name='语文', simplified_name='语', teacher='张三'), \
+ start_time=datetime.time(8, 0, 0), end_time=datetime.time(8, 45, 0))
+ >>> l.subject.name
+ '语文'
+ >>> l.start_time
+ datetime.time(8, 0)
+ >>> l.end_time
+ datetime.time(8, 45)
+ """
+ subject: Subject
+ start_time: datetime.time
+ end_time: datetime.time
+
+
+class WeekType(Enum):
+ """
+ 周次类型。
+ ALL: 适用于所有周
+ ODD: 仅适用于单周
+ EVEN: 仅适用于双周
+ """
+ ALL = "all"
+ ODD = "odd"
+ EVEN = "even"
+
+
+class SingleDaySchedule(BaseModel):
+ """
+ 单日课程安排。
+
+ Args:
+ enable_day (int): 课程安排的星期(如 1 表示星期一)
+ classes (list[Lesson]): 课程列表,每个课程包含科目、开始时间和结束时间
+ name (str): 课程安排名称(如 "星期一")
+ weeks (WeekType): 周次类型,指定课程适用于哪些周次
+
+ Examples:
+ >>> s = SingleDaySchedule(enable_day=1, classes=[Lesson(subject=Subject(name='语文', \
+ simplified_name='语', teacher='张三'), start_time=datetime.time(8, 0, 0), \
+ end_time=datetime.time(8, 45, 0))], name='星期一', weeks=WeekType.ALL)
+ >>> s.enable_day
+ 1
+ >>> s.name
+ '星期一'
+ >>> s.weeks
+
+ """
+ enable_day: int
+ classes: list[Lesson]
+ name: str
+ weeks: WeekType
+
+ def is_enabled_on_week(self, week: int) -> bool:
+ """
+ 判断课程是否在指定的日期上启用。
+
+ Args:
+ week (int): 要检查的周次序号
+
+ Returns:
+ bool: 如果课程在指定周上启用,则返回 True;否则返回 False
+
+ Examples:
+ >>> s = SingleDaySchedule(enable_day=1, classes=[Lesson(subject=Subject(name='语文', \
+ simplified_name='语', teacher='张三'), start_time=datetime.time(8, 0, 0), \
+ end_time=datetime.time(8, 45, 0))], name='星期一', weeks=WeekType.ODD)
+ >>> s.is_enabled_on_week(3)
+ True
+ >>> s.is_enabled_on_week(6)
+ False
+ >>> s.is_enabled_on_week(11)
+ True
+ """
+ return {
+ WeekType.ALL: True, # 适用于所有周 -> 永久启用
+ WeekType.ODD: week % 2 == 1, # 单周
+ WeekType.EVEN: week % 2 == 0 # 双周
+ }[self.weeks]
+
+ def is_enabled_on_day(self, start_day: datetime.date, day: datetime.date) -> bool:
+ """
+ 判断课程是否在指定的日期上启用。
+
+ Args:
+ day (int): 要检查的日期(1 表示星期一,2 表示星期二,依此类推)
+ start_day (datetime.date): 课程开始的日期,用于计算周次
+
+ Returns:
+ bool: 如果课程在指定日期上启用,则返回 True;否则返回 False
+
+ Examples:
+ >>> s = SingleDaySchedule(enable_day=1, classes=[Lesson(subject=Subject(name='语文', \
+ simplified_name='语', teacher='张三'), start_time=datetime.time(8, 0, 0), \
+ end_time=datetime.time(8, 45, 0))], name='星期一', weeks=WeekType.ODD)
+ >>> s.is_enabled_on_day(datetime.date(2025, 9, 1), datetime.date(2025, 9, 4))
+ True
+ >>> s.is_enabled_on_day(datetime.date(2025, 9, 1), datetime.date(2025, 9, 16))
+ True
+ >>> s.is_enabled_on_day(datetime.date(2025, 9, 1), datetime.date(2025, 9, 24))
+ False
+ """
+ return self.is_enabled_on_week(utils.week_num(start_day, day))
+
+
+class Schedule(UserList[SingleDaySchedule]):
+ """
+ 存储每天课程安排的列表。列表会按照星期排序。
+
+ .. caution::
+ 在访问一个Schedule中的项目时,注意索引从 1 开始,而不是从 0 开始。
+ 这是为了可以按照星期访问课表,而不是按照 Python 的逻辑,所以访问星期一的课表使用 ``schedule[1]`` 而不是 ``schedule[0]`` 。
+ 若你想要以 Python 的逻辑访问课表,请使用 ``data`` 属性,如访问星期一的课表需要使用 ``schedule.data[0]`` 。
+
+ Examples:
+ >>> s = Schedule([
+ ... SingleDaySchedule(enable_day=1, classes=[Lesson(subject=Subject(name='语文',
+ ... simplified_name='语', teacher='张三'), start_time=datetime.time(8, 0, 0),
+ ... end_time=datetime.time(8, 45, 0))], name='星期一', weeks=WeekType.ODD),
+ ... SingleDaySchedule(enable_day=2, classes=[Lesson(subject=Subject(name='数学',
+ ... simplified_name='数', teacher='李四'), start_time=datetime.time(9, 0, 0),
+ ... end_time=datetime.time(9, 45, 0))], name='星期二', weeks=WeekType.EVEN)
+ ... ])
+ >>> s[1].enable_day
+ 1
+ """
+ def __init__(self, args: Sequence[SingleDaySchedule]):
+ result = sorted(args, key=lambda arg: arg.enable_day) # 按照启用日期(星期几)排序
+ super().__init__(result)
+
+ @override
+ def __getitem__(self, index: int) -> SingleDaySchedule:
+ return self.data[index - 1]
diff --git a/cses/utils.py b/cses/utils.py
new file mode 100644
index 0000000..a45a33d
--- /dev/null
+++ b/cses/utils.py
@@ -0,0 +1,26 @@
+"""
+该模块包含了一些用于内部处理的辅助函数。您也可以在您的代码中独立调用这些函数。
+"""
+import datetime
+
+
+def week_num(start_day: datetime.date, day: datetime.date) -> int:
+ """
+ 计算指定日期是从开始日期开始的第多少周。
+
+ Args:
+ day (datetime.date): 要计算周次的日期
+ start_day (datetime.date): 课程开始的日期,用于计算周次
+
+ Returns:
+ int: 指定日期是从开始日期开始的第多少周
+
+ Examples:
+ >>> week_num(datetime.date(2025, 9, 1), datetime.date(2025, 9, 4))
+ 1
+ >>> week_num(datetime.date(2025, 9, 1), datetime.date(2025, 9, 16))
+ 3
+ >>> week_num(datetime.date(2025, 9, 1), datetime.date(2025, 10, 24))
+ 8
+ """
+ return (day - start_day).days // 7 + 1
diff --git a/cses_example.yaml b/cses_example.yaml
new file mode 100644
index 0000000..d2fab62
--- /dev/null
+++ b/cses_example.yaml
@@ -0,0 +1,51 @@
+# $schema: ./cses_schema.json
+version: 1 # 只能为 1
+subjects:
+ - name: 数学 # 任意 string
+ simplified_name: 数 # 可选,任意 string,适合中文科目名,ClassIsland 等紧凑课程表软件一般需要
+ teacher: 李梅 # 可选,任意 string
+ room: "101" # 可选,任意 string
+ - name: 语文
+ simplified_name: 语
+ teacher: 王芳
+ room: "102"
+ - name: 英语
+ simplified_name: 英
+ teacher: 张伟
+ room: "103"
+ - name: 物理
+ simplified_name: 物
+ teacher: 赵军
+ room: "104"
+
+schedules:
+ - name: 星期一 # 任意 string
+ enable_day: 1 # 1-7 的整数,即周一到周日
+ weeks: all # all、odd、even 中的任意一个,即两周都开启、单周开启、双周开启
+ classes:
+ - subject: 数学 # 任意 string
+ start_time: "08:00:00" # HH:MM:SS 的任意字符串,即 "00:00:00" 到 "23:59:59"
+ end_time: "09:00:00" # 同 start_time
+ - subject: 语文
+ start_time: "09:00:00"
+ end_time: "10:00:00"
+ - name: 星期二-单周
+ enable_day: 2
+ weeks: odd
+ classes:
+ - subject: 物理
+ start_time: "08:00:00"
+ end_time: "09:00:00"
+ - subject: 英语
+ start_time: "09:00:00"
+ end_time: "10:00:00"
+ - name: 星期二-双周
+ enable_day: 2
+ weeks: even
+ classes:
+ - subject: 物理
+ start_time: "08:00:00"
+ end_time: "09:00:00"
+ - subject: 英语
+ start_time: "09:00:00"
+ end_time: "10:00:00"
\ No newline at end of file
diff --git a/cses_schema.json b/cses_schema.json
new file mode 100644
index 0000000..2947644
--- /dev/null
+++ b/cses_schema.json
@@ -0,0 +1,221 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "type": "object",
+ "definitions": {
+ "class": {
+ "title": "课程安排",
+ "description": "由课程名、开始时间、结束时间组成的课程安排对象。",
+ "type": "object",
+ "properties": {
+ "subject": {
+ "title": "课程名",
+ "type": "string",
+ "description": "subjects 中任一 subject 的 name 字段。",
+ "examples": [
+ "数学",
+ "语文"
+ ]
+ },
+ "start_time": {
+ "title": "开始时间",
+ "description": "课程开始时间,格式为 HH:MM:SS",
+ "type": "string",
+ "pattern": "([01]\\d|2[0-3]):([0-5]\\d):([0-5]\\d)",
+ "examples": [
+ "08:00:00",
+ "13:30:00"
+ ]
+ },
+ "end_time": {
+ "title": "结束时间",
+ "description": "课程结束时间,格式为 HH:MM:SS",
+ "type": "string",
+ "pattern": "([01]\\d|2[0-3]):([0-5]\\d):([0-5]\\d)",
+ "examples": [
+ "08:40:00",
+ "14:10:00"
+ ]
+ }
+ },
+ "required": [
+ "subject",
+ "start_time",
+ "end_time"
+ ]
+ },
+ "schedule": {
+ "title": "课程表",
+ "description": "CSES 日课程表",
+ "type": "object",
+ "properties": {
+ "name": {
+ "title": "名称",
+ "type": "string",
+ "description": "课程表的名称",
+ "examples": [
+ "all-mon"
+ ]
+ },
+ "enable_day": {
+ "title": "启用日",
+ "type": "integer",
+ "description": "启用的日数,1-7 表示周一到周日",
+ "enum": [
+ 1,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6,
+ 7
+ ]
+ },
+ "weeks": {
+ "title": "周数",
+ "type": "string",
+ "description": "周数,all 表示全周,odd 表示单周,even 表示双周",
+ "enum": [
+ "all",
+ "odd",
+ "even"
+ ]
+ },
+ "classes": {
+ "title": "课程",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/class"
+ },
+ "description": "课程安排数组",
+ "examples": [
+ [
+ {
+ "subject": "数学",
+ "start_time": "08:00:00",
+ "end_time": "08:40:00"
+ },
+ {
+ "subject": "语文",
+ "start_time": "13:30:00",
+ "end_time": "14:10:00"
+ }
+ ]
+ ]
+ }
+ },
+ "required": [
+ "name",
+ "enable_day",
+ "weeks",
+ "classes"
+ ]
+ },
+ "subject": {
+ "title": "课程",
+ "description": "课程对象",
+ "type": "object",
+ "properties": {
+ "name": {
+ "title": "名称",
+ "type": "string",
+ "description": "课程名称",
+ "examples": [
+ "数学",
+ "语文"
+ ]
+ },
+ "teacher": {
+ "title": "教师",
+ "type": "string",
+ "description": "任课教师",
+ "examples": [
+ "张三",
+ "李四"
+ ]
+ },
+ "room": {
+ "title": "教室",
+ "type": "string",
+ "description": "上课教室",
+ "examples": [
+ "101",
+ "102"
+ ]
+ },
+ "simplified_name": {
+ "title": "简称",
+ "type": "string",
+ "description": "课程简称",
+ "examples": [
+ "数",
+ "语"
+ ]
+ }
+ },
+ "required": [
+ "name"
+ ]
+ }
+ },
+ "properties": {
+ "version": {
+ "const": 1,
+ "type": "integer",
+ "title": "Version",
+ "description": "CSES 的版本号"
+ },
+ "subjects": {
+ "title": "课程列表",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/subject"
+ },
+ "description": "课程列表",
+ "examples": [
+ [
+ {
+ "name": "数学",
+ "teacher": "张三",
+ "room": "101",
+ "simplified_name": "数"
+ },
+ {
+ "name": "语文",
+ "teacher": "李四",
+ "room": "102",
+ "simplified_name": "语"
+ }
+ ]
+ ]
+ },
+ "schedules": {
+ "title": "课程表列表",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/schedule"
+ },
+ "description": "课程表列表",
+ "examples": [
+ [
+ {
+ "name": "all-mon",
+ "enable_day": 1,
+ "weeks": "all",
+ "classes": [
+ {
+ "subject": "数学",
+ "start_time": "08:50:00",
+ "end_time": "08:40:00"
+ },
+ {
+ "subject": "语文",
+ "start_time": "13:30:00",
+ "end_time": "14:10:00"
+ }
+ ]
+ }
+ ]
+ ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/docs/Makefile b/docs/Makefile
new file mode 100644
index 0000000..d0c3cbf
--- /dev/null
+++ b/docs/Makefile
@@ -0,0 +1,20 @@
+# Minimal makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line, and also
+# from the environment for the first two.
+SPHINXOPTS ?=
+SPHINXBUILD ?= sphinx-build
+SOURCEDIR = source
+BUILDDIR = build
+
+# Put it first so that "make" without argument is like "make help".
+help:
+ @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
+
+.PHONY: help Makefile
+
+# Catch-all target: route all unknown targets to Sphinx using the new
+# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
+%: Makefile
+ @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
diff --git a/docs/make.bat b/docs/make.bat
new file mode 100644
index 0000000..dc1312a
--- /dev/null
+++ b/docs/make.bat
@@ -0,0 +1,35 @@
+@ECHO OFF
+
+pushd %~dp0
+
+REM Command file for Sphinx documentation
+
+if "%SPHINXBUILD%" == "" (
+ set SPHINXBUILD=sphinx-build
+)
+set SOURCEDIR=source
+set BUILDDIR=build
+
+%SPHINXBUILD% >NUL 2>NUL
+if errorlevel 9009 (
+ echo.
+ echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
+ echo.installed, then set the SPHINXBUILD environment variable to point
+ echo.to the full path of the 'sphinx-build' executable. Alternatively you
+ echo.may add the Sphinx directory to PATH.
+ echo.
+ echo.If you don't have Sphinx installed, grab it from
+ echo.https://www.sphinx-doc.org/
+ exit /b 1
+)
+
+if "%1" == "" goto help
+
+%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
+goto end
+
+:help
+%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
+
+:end
+popd
diff --git a/docs/source/conf.py b/docs/source/conf.py
new file mode 100644
index 0000000..c45a657
--- /dev/null
+++ b/docs/source/conf.py
@@ -0,0 +1,36 @@
+# Configuration file for the Sphinx documentation builder.
+#
+# For the full list of built-in configuration values, see the documentation:
+# https://www.sphinx-doc.org/en/master/usage/configuration.html
+
+# -- Project information -----------------------------------------------------
+# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
+
+project = 'cseslib4py'
+copyright = '2025, MacrosMeng'
+author = 'MacrosMeng'
+
+# -- General configuration ---------------------------------------------------
+# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
+
+import os
+import sys
+
+sys.path.insert(0, os.path.abspath('../../'))
+
+extensions = [
+ 'sphinx.ext.napoleon',
+ 'sphinx.ext.autodoc',
+ 'sphinx.ext.viewcode',
+]
+
+templates_path = ['_templates']
+exclude_patterns = []
+
+language = 'zh-cn'
+
+# -- Options for HTML output -------------------------------------------------
+# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
+
+html_theme = 'furo'
+html_static_path = ['_static']
diff --git a/docs/source/err.rst b/docs/source/err.rst
new file mode 100644
index 0000000..40f8116
--- /dev/null
+++ b/docs/source/err.rst
@@ -0,0 +1,5 @@
+错误类型
+========
+
+.. automodule:: cses.errors
+ :members:
diff --git a/docs/source/index.rst b/docs/source/index.rst
new file mode 100644
index 0000000..ec023a7
--- /dev/null
+++ b/docs/source/index.rst
@@ -0,0 +1,29 @@
+cseslib4py 文档
+===================
+
+ | 适用于 Python 的 CSES API
+
+简介
+----
+
+欢迎来到 ``cseslib4py`` 的文档页面。
+
+**cseslib4py** 是一个 Python 库,提供对 `CSES `_ 格式的访问,同时具有简单易用的设计。
+
+此外,它由 `MacrosMeng `_ 重构,以提供比已经停更的 `pycses `_ **更好** 和 **具有更高易读性** 的访问。
+
+许可证
+------
+**cseslib4py** 采用 **MIT** 许可证。您可以在遵守许可证条款的前提下自由使用、修改和分发该库。
+
+
+
+.. toctree::
+ :maxdepth: 1
+ :caption: 目录
+
+ Home
+ main_usage
+ structs
+ err
+ utils
diff --git a/docs/source/main_usage.rst b/docs/source/main_usage.rst
new file mode 100644
index 0000000..22e27d4
--- /dev/null
+++ b/docs/source/main_usage.rst
@@ -0,0 +1,5 @@
+主要用法
+========
+
+.. automodule:: cses
+ :members:
diff --git a/docs/source/structs.rst b/docs/source/structs.rst
new file mode 100644
index 0000000..427ca91
--- /dev/null
+++ b/docs/source/structs.rst
@@ -0,0 +1,5 @@
+数据结构
+========
+
+.. automodule:: cses.structures
+ :members:
diff --git a/docs/source/utils.rst b/docs/source/utils.rst
new file mode 100644
index 0000000..a737265
--- /dev/null
+++ b/docs/source/utils.rst
@@ -0,0 +1,5 @@
+工具函数
+========
+
+.. automodule:: cses.utils
+ :members:
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..37cc73f
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,49 @@
+accessible-pygments==0.0.5
+alabaster==1.0.0
+annotated-types==0.7.0
+anyio==4.11.0
+babel==2.17.0
+beautifulsoup4==4.14.2
+certifi==2025.10.5
+charset-normalizer==3.4.4
+click==8.3.0
+colorama==0.4.6
+commonmark==0.9.1
+docutils==0.21.2
+furo==2025.9.25
+h11==0.16.0
+idna==3.11
+imagesize==1.4.1
+Jinja2==3.1.6
+Markdown==3.9
+MarkupSafe==3.0.3
+packaging==25.0
+pydantic==2.12.3
+pydantic_core==2.41.4
+Pygments==2.19.2
+PyYAML==6.0.3
+recommonmark==0.7.1
+requests==2.32.5
+roman-numerals-py==3.1.0
+setuptools==80.9.0
+sniffio==1.3.1
+snowballstemmer==3.0.1
+soupsieve==2.8
+Sphinx==8.2.3
+sphinx-autobuild==2025.8.25
+sphinx-basic-ng==1.0.0b2
+sphinx-markdown-tables==0.0.17
+sphinxcontrib-applehelp==2.0.0
+sphinxcontrib-devhelp==2.0.0
+sphinxcontrib-htmlhelp==2.1.0
+sphinxcontrib-jquery==4.1
+sphinxcontrib-jsmath==1.0.1
+sphinxcontrib-qthelp==2.0.0
+sphinxcontrib-serializinghtml==2.0.0
+starlette==0.48.0
+typing-inspection==0.4.2
+typing_extensions==4.15.0
+urllib3==2.5.0
+uvicorn==0.38.0
+watchfiles==1.1.1
+websockets==15.0.1
diff --git a/setup.py b/setup.py
index 8f678ee..3a20f63 100644
--- a/setup.py
+++ b/setup.py
@@ -8,15 +8,16 @@
description='CSES access framework for Python',
long_description=open('README.md', encoding="utf-8").read(),
long_description_content_type='text/markdown',
- url='https://github.com/SmartTeachCN/pycses',
+ url='https://github.com/MacroMeng/cseslib4py',
packages=find_packages(),
classifiers=[
'Programming Language :: Python :: 3',
'License :: OSI Approved :: MIT License',
'Operating System :: OS Independent',
],
- python_requires='>=3.6',
+ python_requires='>=3.9',
install_requires=[
'PyYAML>=5.4.1',
+ 'pydantic~=2.12.3',
],
)