course-format-ts 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.idea/edu-format-ts.iml +10 -0
- package/.idea/inspectionProfiles/Project_Default.xml +28 -0
- package/.idea/misc.xml +4 -0
- package/.idea/modules.xml +8 -0
- package/.idea/vcs.xml +6 -0
- package/AGENTS.md +1 -0
- package/dist/courseFormat/AnswerPlaceholder.d.ts +37 -0
- package/dist/courseFormat/AnswerPlaceholder.js +101 -0
- package/dist/courseFormat/AnswerPlaceholderComparator.d.ts +4 -0
- package/dist/courseFormat/AnswerPlaceholderComparator.js +8 -0
- package/dist/courseFormat/AnswerPlaceholderDependency.d.ts +19 -0
- package/dist/courseFormat/AnswerPlaceholderDependency.js +91 -0
- package/dist/courseFormat/CheckFeedback.d.ts +20 -0
- package/dist/courseFormat/CheckFeedback.js +58 -0
- package/dist/courseFormat/CheckResult.d.ts +33 -0
- package/dist/courseFormat/CheckResult.js +58 -0
- package/dist/courseFormat/CheckResultSeverity.d.ts +7 -0
- package/dist/courseFormat/CheckResultSeverity.js +17 -0
- package/dist/courseFormat/CheckStatus.d.ts +5 -0
- package/dist/courseFormat/CheckStatus.js +9 -0
- package/dist/courseFormat/Course.d.ts +68 -0
- package/dist/courseFormat/Course.js +165 -0
- package/dist/courseFormat/CourseMode.d.ts +4 -0
- package/dist/courseFormat/CourseMode.js +8 -0
- package/dist/courseFormat/CourseVisibility.d.ts +4 -0
- package/dist/courseFormat/CourseVisibility.js +8 -0
- package/dist/courseFormat/CourseraCourse.d.ts +5 -0
- package/dist/courseFormat/CourseraCourse.js +15 -0
- package/dist/courseFormat/DescriptionFormat.d.ts +5 -0
- package/dist/courseFormat/DescriptionFormat.js +9 -0
- package/dist/courseFormat/EduCourse.d.ts +17 -0
- package/dist/courseFormat/EduCourse.js +42 -0
- package/dist/courseFormat/EduFile.d.ts +22 -0
- package/dist/courseFormat/EduFile.js +110 -0
- package/dist/courseFormat/EduFileErrorHighlightLevel.d.ts +5 -0
- package/dist/courseFormat/EduFileErrorHighlightLevel.js +9 -0
- package/dist/courseFormat/EduFormatNames.d.ts +75 -0
- package/dist/courseFormat/EduFormatNames.js +80 -0
- package/dist/courseFormat/EduTestInfo.d.ts +28 -0
- package/dist/courseFormat/EduTestInfo.js +68 -0
- package/dist/courseFormat/EduVersions.d.ts +2 -0
- package/dist/courseFormat/EduVersions.js +5 -0
- package/dist/courseFormat/FileContents.d.ts +36 -0
- package/dist/courseFormat/FileContents.js +67 -0
- package/dist/courseFormat/FileContentsFactory.d.ts +17 -0
- package/dist/courseFormat/FileContentsFactory.js +2 -0
- package/dist/courseFormat/FrameworkLesson.d.ts +9 -0
- package/dist/courseFormat/FrameworkLesson.js +28 -0
- package/dist/courseFormat/ItemContainer.d.ts +13 -0
- package/dist/courseFormat/ItemContainer.js +45 -0
- package/dist/courseFormat/JBAccountUserInfo.d.ts +9 -0
- package/dist/courseFormat/JBAccountUserInfo.js +20 -0
- package/dist/courseFormat/Language.d.ts +4 -0
- package/dist/courseFormat/Language.js +33 -0
- package/dist/courseFormat/Lesson.d.ts +20 -0
- package/dist/courseFormat/Lesson.js +56 -0
- package/dist/courseFormat/LessonContainer.d.ts +16 -0
- package/dist/courseFormat/LessonContainer.js +54 -0
- package/dist/courseFormat/PluginInfo.d.ts +7 -0
- package/dist/courseFormat/PluginInfo.js +15 -0
- package/dist/courseFormat/Section.d.ts +8 -0
- package/dist/courseFormat/Section.js +27 -0
- package/dist/courseFormat/StudyItem.d.ts +20 -0
- package/dist/courseFormat/StudyItem.js +47 -0
- package/dist/courseFormat/Tags.d.ts +16 -0
- package/dist/courseFormat/Tags.js +42 -0
- package/dist/courseFormat/TaskFile.d.ts +26 -0
- package/dist/courseFormat/TaskFile.js +72 -0
- package/dist/courseFormat/UserInfo.d.ts +3 -0
- package/dist/courseFormat/UserInfo.js +2 -0
- package/dist/courseFormat/Vendor.d.ts +7 -0
- package/dist/courseFormat/Vendor.js +14 -0
- package/dist/courseFormat/attempts/Attempt.d.ts +12 -0
- package/dist/courseFormat/attempts/Attempt.js +25 -0
- package/dist/courseFormat/attempts/AttemptBase.d.ts +9 -0
- package/dist/courseFormat/attempts/AttemptBase.js +28 -0
- package/dist/courseFormat/attempts/DataTaskAttempt.d.ts +6 -0
- package/dist/courseFormat/attempts/DataTaskAttempt.js +24 -0
- package/dist/courseFormat/attempts/Dataset.d.ts +12 -0
- package/dist/courseFormat/attempts/Dataset.js +21 -0
- package/dist/courseFormat/fileUtils.d.ts +5 -0
- package/dist/courseFormat/fileUtils.js +32 -0
- package/dist/courseFormat/hyperskill/HyperskillCourse.d.ts +13 -0
- package/dist/courseFormat/hyperskill/HyperskillCourse.js +25 -0
- package/dist/courseFormat/hyperskill/HyperskillProject.d.ts +10 -0
- package/dist/courseFormat/hyperskill/HyperskillProject.js +16 -0
- package/dist/courseFormat/hyperskill/HyperskillStage.d.ts +8 -0
- package/dist/courseFormat/hyperskill/HyperskillStage.js +20 -0
- package/dist/courseFormat/hyperskill/HyperskillTaskType.d.ts +4 -0
- package/dist/courseFormat/hyperskill/HyperskillTaskType.js +26 -0
- package/dist/courseFormat/hyperskill/HyperskillTopic.d.ts +5 -0
- package/dist/courseFormat/hyperskill/HyperskillTopic.js +11 -0
- package/dist/courseFormat/loggerUtils.d.ts +1 -0
- package/dist/courseFormat/loggerUtils.js +6 -0
- package/dist/courseFormat/stepik/StepikCourse.d.ts +5 -0
- package/dist/courseFormat/stepik/StepikCourse.js +15 -0
- package/dist/courseFormat/stepik/StepikLesson.d.ts +6 -0
- package/dist/courseFormat/stepik/StepikLesson.js +16 -0
- package/dist/courseFormat/tasks/AnswerTask.d.ts +8 -0
- package/dist/courseFormat/tasks/AnswerTask.js +11 -0
- package/dist/courseFormat/tasks/CodeTask.d.ts +12 -0
- package/dist/courseFormat/tasks/CodeTask.js +21 -0
- package/dist/courseFormat/tasks/DataTask.d.ts +18 -0
- package/dist/courseFormat/tasks/DataTask.js +32 -0
- package/dist/courseFormat/tasks/EduTask.d.ts +12 -0
- package/dist/courseFormat/tasks/EduTask.js +22 -0
- package/dist/courseFormat/tasks/IdeTask.d.ts +9 -0
- package/dist/courseFormat/tasks/IdeTask.js +14 -0
- package/dist/courseFormat/tasks/NumberTask.d.ts +9 -0
- package/dist/courseFormat/tasks/NumberTask.js +14 -0
- package/dist/courseFormat/tasks/OutputTask.d.ts +10 -0
- package/dist/courseFormat/tasks/OutputTask.js +18 -0
- package/dist/courseFormat/tasks/OutputTaskBase.d.ts +14 -0
- package/dist/courseFormat/tasks/OutputTaskBase.js +19 -0
- package/dist/courseFormat/tasks/RemoteEduTask.d.ts +9 -0
- package/dist/courseFormat/tasks/RemoteEduTask.js +15 -0
- package/dist/courseFormat/tasks/StringTask.d.ts +9 -0
- package/dist/courseFormat/tasks/StringTask.js +14 -0
- package/dist/courseFormat/tasks/TableTask.d.ts +17 -0
- package/dist/courseFormat/tasks/TableTask.js +43 -0
- package/dist/courseFormat/tasks/Task.d.ts +45 -0
- package/dist/courseFormat/tasks/Task.js +155 -0
- package/dist/courseFormat/tasks/TheoryTask.d.ts +10 -0
- package/dist/courseFormat/tasks/TheoryTask.js +15 -0
- package/dist/courseFormat/tasks/UnsupportedTask.d.ts +9 -0
- package/dist/courseFormat/tasks/UnsupportedTask.js +14 -0
- package/dist/courseFormat/tasks/choice/ChoiceOption.d.ts +10 -0
- package/dist/courseFormat/tasks/choice/ChoiceOption.js +33 -0
- package/dist/courseFormat/tasks/choice/ChoiceOptionStatus.d.ts +5 -0
- package/dist/courseFormat/tasks/choice/ChoiceOptionStatus.js +9 -0
- package/dist/courseFormat/tasks/choice/ChoiceTask.d.ts +23 -0
- package/dist/courseFormat/tasks/choice/ChoiceTask.js +47 -0
- package/dist/courseFormat/tasks/matching/MatchingTask.d.ts +10 -0
- package/dist/courseFormat/tasks/matching/MatchingTask.js +15 -0
- package/dist/courseFormat/tasks/matching/SortingBasedTask.d.ts +16 -0
- package/dist/courseFormat/tasks/matching/SortingBasedTask.js +50 -0
- package/dist/courseFormat/tasks/matching/SortingTask.d.ts +9 -0
- package/dist/courseFormat/tasks/matching/SortingTask.js +14 -0
- package/dist/courseFormat/uiMessages.d.ts +3 -0
- package/dist/courseFormat/uiMessages.js +14 -0
- package/dist/disk-loader.d.ts +4 -0
- package/dist/disk-loader.js +389 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.js +64 -0
- package/dist/loader.d.ts +7 -0
- package/dist/loader.js +435 -0
- package/dist/models.d.ts +49 -0
- package/dist/models.js +2 -0
- package/dist/zip-loader.d.ts +4 -0
- package/dist/zip-loader.js +431 -0
- package/example-course-project/course-info.yaml +15 -0
- package/example-course-project/lesson1/lesson-info.yaml +3 -0
- package/example-course-project/lesson1/lesson-remote-info.yaml +1 -0
- package/example-course-project/lesson1/task1/Task.txt +1 -0
- package/example-course-project/lesson1/task1/task-info.yaml +12 -0
- package/example-course-project/lesson1/task1/task-remote-info.yaml +1 -0
- package/example-course-project/lesson1/task1/task.md +47 -0
- package/example-course-project/lesson1/task1/tests/Tests.txt +0 -0
- package/example-course-project/lesson1/task2/Task.txt +1 -0
- package/example-course-project/lesson1/task2/task-info.yaml +12 -0
- package/example-course-project/lesson1/task2/task-remote-info.yaml +1 -0
- package/example-course-project/lesson1/task2/task.md +47 -0
- package/example-course-project/lesson1/task2/tests/Tests.txt +0 -0
- package/package.json +19 -0
- package/src/@types/mime-types.d.ts +3 -0
- package/src/courseFormat/AnswerPlaceholder.ts +121 -0
- package/src/courseFormat/AnswerPlaceholderComparator.ts +7 -0
- package/src/courseFormat/AnswerPlaceholderDependency.ts +122 -0
- package/src/courseFormat/CheckFeedback.ts +71 -0
- package/src/courseFormat/CheckResult.ts +92 -0
- package/src/courseFormat/CheckResultSeverity.ts +13 -0
- package/src/courseFormat/CheckStatus.ts +5 -0
- package/src/courseFormat/Course.ts +201 -0
- package/src/courseFormat/CourseMode.ts +4 -0
- package/src/courseFormat/CourseVisibility.ts +4 -0
- package/src/courseFormat/CourseraCourse.ts +10 -0
- package/src/courseFormat/DescriptionFormat.ts +5 -0
- package/src/courseFormat/EduCourse.ts +41 -0
- package/src/courseFormat/EduFile.ts +133 -0
- package/src/courseFormat/EduFileErrorHighlightLevel.ts +5 -0
- package/src/courseFormat/EduFormatNames.ts +95 -0
- package/src/courseFormat/EduTestInfo.ts +87 -0
- package/src/courseFormat/EduVersions.ts +2 -0
- package/src/courseFormat/FileContents.ts +97 -0
- package/src/courseFormat/FileContentsFactory.ts +19 -0
- package/src/courseFormat/FrameworkLesson.ts +29 -0
- package/src/courseFormat/ItemContainer.ts +47 -0
- package/src/courseFormat/JBAccountUserInfo.ts +21 -0
- package/src/courseFormat/Language.ts +31 -0
- package/src/courseFormat/Lesson.ts +69 -0
- package/src/courseFormat/LessonContainer.ts +65 -0
- package/src/courseFormat/PluginInfo.ts +15 -0
- package/src/courseFormat/Section.ts +29 -0
- package/src/courseFormat/StudyItem.ts +55 -0
- package/src/courseFormat/Tags.ts +45 -0
- package/src/courseFormat/TaskFile.ts +88 -0
- package/src/courseFormat/UserInfo.ts +3 -0
- package/src/courseFormat/Vendor.ts +15 -0
- package/src/courseFormat/attempts/Attempt.ts +28 -0
- package/src/courseFormat/attempts/AttemptBase.ts +24 -0
- package/src/courseFormat/attempts/DataTaskAttempt.ts +19 -0
- package/src/courseFormat/attempts/Dataset.ts +13 -0
- package/src/courseFormat/fileUtils.ts +31 -0
- package/src/courseFormat/hyperskill/HyperskillCourse.ts +24 -0
- package/src/courseFormat/hyperskill/HyperskillProject.ts +10 -0
- package/src/courseFormat/hyperskill/HyperskillStage.ts +15 -0
- package/src/courseFormat/hyperskill/HyperskillTaskType.ts +23 -0
- package/src/courseFormat/hyperskill/HyperskillTopic.ts +5 -0
- package/src/courseFormat/loggerUtils.ts +3 -0
- package/src/courseFormat/stepik/StepikCourse.ts +10 -0
- package/src/courseFormat/stepik/StepikLesson.ts +11 -0
- package/src/courseFormat/tasks/AnswerTask.ts +13 -0
- package/src/courseFormat/tasks/CodeTask.ts +42 -0
- package/src/courseFormat/tasks/DataTask.ts +37 -0
- package/src/courseFormat/tasks/EduTask.ts +26 -0
- package/src/courseFormat/tasks/IdeTask.ts +17 -0
- package/src/courseFormat/tasks/NumberTask.ts +17 -0
- package/src/courseFormat/tasks/OutputTask.ts +21 -0
- package/src/courseFormat/tasks/OutputTaskBase.ts +23 -0
- package/src/courseFormat/tasks/RemoteEduTask.ts +18 -0
- package/src/courseFormat/tasks/StringTask.ts +17 -0
- package/src/courseFormat/tasks/TableTask.ts +51 -0
- package/src/courseFormat/tasks/Task.ts +181 -0
- package/src/courseFormat/tasks/TheoryTask.ts +19 -0
- package/src/courseFormat/tasks/UnsupportedTask.ts +17 -0
- package/src/courseFormat/tasks/choice/ChoiceOption.ts +37 -0
- package/src/courseFormat/tasks/choice/ChoiceOptionStatus.ts +5 -0
- package/src/courseFormat/tasks/choice/ChoiceTask.ts +57 -0
- package/src/courseFormat/tasks/matching/MatchingTask.ts +19 -0
- package/src/courseFormat/tasks/matching/SortingBasedTask.ts +59 -0
- package/src/courseFormat/tasks/matching/SortingTask.ts +17 -0
- package/src/courseFormat/uiMessages.ts +12 -0
- package/src/disk-loader.ts +463 -0
- package/src/index.ts +33 -0
- package/src/models.ts +54 -0
- package/src/zip-loader.ts +583 -0
- package/test/load-course.test.js +279 -0
- package/test/load-zip-course.test.js +73 -0
- package/tsconfig.json +15 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { CheckStatus } from "./CheckStatus"
|
|
2
|
+
import { TaskFile } from "./TaskFile"
|
|
3
|
+
import { AnswerPlaceholderDependency } from "./AnswerPlaceholderDependency"
|
|
4
|
+
|
|
5
|
+
export class AnswerPlaceholder {
|
|
6
|
+
offset = -1
|
|
7
|
+
length = -1
|
|
8
|
+
index = -1
|
|
9
|
+
|
|
10
|
+
private _initialState?: MyInitialState
|
|
11
|
+
private _taskFile?: TaskFile
|
|
12
|
+
|
|
13
|
+
placeholderDependency?: AnswerPlaceholderDependency
|
|
14
|
+
isInitializedFromDependency = false
|
|
15
|
+
possibleAnswer = ""
|
|
16
|
+
placeholderText = ""
|
|
17
|
+
selected = false
|
|
18
|
+
status: CheckStatus = CheckStatus.Unchecked
|
|
19
|
+
isVisible = true
|
|
20
|
+
studentAnswer?: string
|
|
21
|
+
|
|
22
|
+
constructor()
|
|
23
|
+
constructor(offset: number, placeholderText: string)
|
|
24
|
+
constructor(offset?: number, placeholderText?: string) {
|
|
25
|
+
if (offset !== undefined && placeholderText !== undefined) {
|
|
26
|
+
this.offset = offset
|
|
27
|
+
this.length = placeholderText.length
|
|
28
|
+
this.placeholderText = placeholderText
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
get initialState(): MyInitialState {
|
|
33
|
+
if (!this._initialState) {
|
|
34
|
+
throw new Error("No initial state for answer placeholder")
|
|
35
|
+
}
|
|
36
|
+
return this._initialState
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
set initialState(value: MyInitialState) {
|
|
40
|
+
this._initialState = value
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
get taskFile(): TaskFile {
|
|
44
|
+
if (!this._taskFile) {
|
|
45
|
+
throw new Error(`Task file is null for answer placeholder`)
|
|
46
|
+
}
|
|
47
|
+
return this._taskFile
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
set taskFile(value: TaskFile) {
|
|
51
|
+
this._taskFile = value
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
init(file: TaskFile, isRestarted: boolean): void {
|
|
55
|
+
this.taskFile = file
|
|
56
|
+
if (isRestarted) return
|
|
57
|
+
if (this.placeholderDependency) {
|
|
58
|
+
this.placeholderDependency.answerPlaceholder = this
|
|
59
|
+
}
|
|
60
|
+
this.initialState = new MyInitialState(this.offset, this.length)
|
|
61
|
+
this.status = file.task.status
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
reset(revertStartOffset: boolean): void {
|
|
65
|
+
if (revertStartOffset) {
|
|
66
|
+
this.offset = this.initialState.offset
|
|
67
|
+
}
|
|
68
|
+
this.length = this.initialState.length
|
|
69
|
+
this.status = CheckStatus.Unchecked
|
|
70
|
+
this.isInitializedFromDependency = false
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
initEmpty(): void {
|
|
74
|
+
this.initialState = new MyInitialState(this.offset, this.placeholderText.length)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
get isCurrentlyVisible(): boolean {
|
|
78
|
+
let result = this.shouldBeVisible
|
|
79
|
+
if (this.placeholderDependency) {
|
|
80
|
+
result = result || !this.isInitializedFromDependency
|
|
81
|
+
}
|
|
82
|
+
return result
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
get shouldBeVisible(): boolean {
|
|
86
|
+
if (!this.placeholderDependency) {
|
|
87
|
+
return this.isVisible
|
|
88
|
+
}
|
|
89
|
+
return this.isVisible && this.placeholderDependency.isVisible
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
isValid(textLength: number): boolean {
|
|
93
|
+
return this.offset >= 0 && this.length >= 0 && this.endOffset <= textLength
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
get endOffset(): number {
|
|
97
|
+
return this.offset + this.length
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
toString(): string {
|
|
101
|
+
const task = this.taskFile.task
|
|
102
|
+
const lesson = task.lesson
|
|
103
|
+
const section = lesson.section
|
|
104
|
+
const sectionPrefix = section ? `${section.name}#` : ""
|
|
105
|
+
return `${sectionPrefix}${lesson.name}#${task.name}#${this.taskFile.name}[${this.offset}, ${this.endOffset}]`
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export class MyInitialState {
|
|
110
|
+
length = -1
|
|
111
|
+
offset = -1
|
|
112
|
+
|
|
113
|
+
constructor(initialOffset?: number, initialLength?: number) {
|
|
114
|
+
if (initialOffset !== undefined) {
|
|
115
|
+
this.offset = initialOffset
|
|
116
|
+
}
|
|
117
|
+
if (initialLength !== undefined) {
|
|
118
|
+
this.length = initialLength
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { AnswerPlaceholder } from "./AnswerPlaceholder"
|
|
2
|
+
import { Course } from "./Course"
|
|
3
|
+
import { Lesson } from "./Lesson"
|
|
4
|
+
import { message } from "./uiMessages"
|
|
5
|
+
|
|
6
|
+
const DEPENDENCY_PATTERN = /(([^#]+)#)?([^#]+)#([^#]+)#([^#]+)#(\d+)/
|
|
7
|
+
|
|
8
|
+
export class AnswerPlaceholderDependency {
|
|
9
|
+
sectionName?: string
|
|
10
|
+
lessonName = ""
|
|
11
|
+
taskName = ""
|
|
12
|
+
fileName = ""
|
|
13
|
+
placeholderIndex = 0
|
|
14
|
+
isVisible = true
|
|
15
|
+
answerPlaceholder!: AnswerPlaceholder
|
|
16
|
+
|
|
17
|
+
constructor(
|
|
18
|
+
answerPlaceholder?: AnswerPlaceholder,
|
|
19
|
+
sectionName?: string | null,
|
|
20
|
+
lessonName = "",
|
|
21
|
+
taskName = "",
|
|
22
|
+
fileName = "",
|
|
23
|
+
placeholderIndex = 0,
|
|
24
|
+
isVisible = true
|
|
25
|
+
) {
|
|
26
|
+
if (answerPlaceholder) {
|
|
27
|
+
this.answerPlaceholder = answerPlaceholder
|
|
28
|
+
}
|
|
29
|
+
this.sectionName = sectionName ?? undefined
|
|
30
|
+
this.lessonName = lessonName
|
|
31
|
+
this.taskName = taskName
|
|
32
|
+
this.fileName = fileName
|
|
33
|
+
this.placeholderIndex = placeholderIndex
|
|
34
|
+
this.isVisible = isVisible
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
resolve(course: Course): AnswerPlaceholder | undefined {
|
|
38
|
+
const lesson = course.getLesson(this.sectionName, this.lessonName)
|
|
39
|
+
const task = lesson?.getTask(this.taskName)
|
|
40
|
+
const taskFile = task?.getTaskFile(this.fileName)
|
|
41
|
+
return taskFile?.answerPlaceholders[this.placeholderIndex]
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
toString(): string {
|
|
45
|
+
const section = this.sectionName ? `${this.sectionName}#` : ""
|
|
46
|
+
return `${section}${this.lessonName}#${this.taskName}#${this.fileName}#${this.placeholderIndex + 1}`
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
static create(answerPlaceholder: AnswerPlaceholder, text: string): AnswerPlaceholderDependency | null {
|
|
50
|
+
if (!text.trim()) {
|
|
51
|
+
return null
|
|
52
|
+
}
|
|
53
|
+
const task = answerPlaceholder.taskFile.task
|
|
54
|
+
const match = text.match(DEPENDENCY_PATTERN)
|
|
55
|
+
if (!match) {
|
|
56
|
+
throw new InvalidDependencyException(text)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const sectionName = match[2]
|
|
60
|
+
const lessonName = match[3]
|
|
61
|
+
const taskName = match[4]
|
|
62
|
+
const filePath = toSystemIndependent(match[5])
|
|
63
|
+
const placeholderIndex = Number.parseInt(match[6], 10) - 1
|
|
64
|
+
if (Number.isNaN(placeholderIndex)) {
|
|
65
|
+
throw new InvalidDependencyException(text)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const dependency = new AnswerPlaceholderDependency(
|
|
69
|
+
answerPlaceholder,
|
|
70
|
+
sectionName,
|
|
71
|
+
lessonName,
|
|
72
|
+
taskName,
|
|
73
|
+
filePath,
|
|
74
|
+
placeholderIndex,
|
|
75
|
+
true
|
|
76
|
+
)
|
|
77
|
+
const targetPlaceholder = dependency.resolve(task.course)
|
|
78
|
+
if (!targetPlaceholder) {
|
|
79
|
+
throw new InvalidDependencyException(text, message("exception.placeholder.non.existing"))
|
|
80
|
+
}
|
|
81
|
+
if (targetPlaceholder.taskFile.task === task) {
|
|
82
|
+
throw new InvalidDependencyException(text, message("exception.placeholder.wrong.reference.to.source"))
|
|
83
|
+
}
|
|
84
|
+
if (refersToNextTask(task, targetPlaceholder.taskFile.task)) {
|
|
85
|
+
throw new InvalidDependencyException(text, message("exception.placeholder.wrong.reference.to.next"))
|
|
86
|
+
}
|
|
87
|
+
return dependency
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export class InvalidDependencyException extends Error {
|
|
92
|
+
readonly customMessage: string
|
|
93
|
+
|
|
94
|
+
constructor(dependencyText: string, customMessage?: string) {
|
|
95
|
+
super(
|
|
96
|
+
customMessage
|
|
97
|
+
? `'${dependencyText}' is not a valid placeholder dependency\n${customMessage}`
|
|
98
|
+
: `'${dependencyText}' is not a valid placeholder dependency`
|
|
99
|
+
)
|
|
100
|
+
this.customMessage = customMessage ?? message("exception.placeholder.invalid.dependency")
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function toSystemIndependent(pathValue: string): string {
|
|
105
|
+
return pathValue.replace(/\\/g, "/")
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function refersToNextTask(sourceTask: { lesson: Lesson; index: number }, targetTask: { lesson: Lesson; index: number }): boolean {
|
|
109
|
+
const sourceLesson = sourceTask.lesson
|
|
110
|
+
const targetLesson = targetTask.lesson
|
|
111
|
+
if (sourceLesson === targetLesson) {
|
|
112
|
+
return targetTask.index > sourceTask.index
|
|
113
|
+
}
|
|
114
|
+
if (sourceLesson.section === targetLesson.section) {
|
|
115
|
+
return targetLesson.index > sourceLesson.index
|
|
116
|
+
}
|
|
117
|
+
return getIndexInCourse(targetLesson) > getIndexInCourse(sourceLesson)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function getIndexInCourse(lesson: Lesson): number {
|
|
121
|
+
return lesson.section?.index ?? lesson.index
|
|
122
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { CheckResult, CheckResultDiff } from "./CheckResult"
|
|
2
|
+
import { CheckStatus } from "./CheckStatus"
|
|
3
|
+
import { EduTestInfo } from "./EduTestInfo"
|
|
4
|
+
import { logger } from "./loggerUtils"
|
|
5
|
+
|
|
6
|
+
export class CheckFeedback {
|
|
7
|
+
readonly failedTestInfo?: EduTestInfo
|
|
8
|
+
readonly time?: Date
|
|
9
|
+
private _message = ""
|
|
10
|
+
private _expected?: string
|
|
11
|
+
private _actual?: string
|
|
12
|
+
|
|
13
|
+
constructor(
|
|
14
|
+
failedTestInfo?: EduTestInfo | null,
|
|
15
|
+
time?: Date | null
|
|
16
|
+
) {
|
|
17
|
+
this.failedTestInfo = failedTestInfo ?? undefined
|
|
18
|
+
this.time = time ?? undefined
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
static fromMessage(message: string, time?: Date | null, expected?: string | null, actual?: string | null): CheckFeedback {
|
|
22
|
+
const feedback = new CheckFeedback(undefined, time ?? undefined)
|
|
23
|
+
feedback.message = message
|
|
24
|
+
feedback.expected = expected ?? undefined
|
|
25
|
+
feedback.actual = actual ?? undefined
|
|
26
|
+
return feedback
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
static fromCheckResult(time: Date, checkResult: CheckResult): CheckFeedback {
|
|
30
|
+
const feedback = new CheckFeedback(checkResult.executedTestsInfo[0], time)
|
|
31
|
+
feedback.expected = checkResult.diffResolved?.expected
|
|
32
|
+
feedback.actual = checkResult.diffResolved?.actual
|
|
33
|
+
feedback.message = checkResult.messageResolved
|
|
34
|
+
return feedback
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
get message(): string {
|
|
38
|
+
return this._message || this.failedTestInfo?.message || ""
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
set message(value: string) {
|
|
42
|
+
this._message = value
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
get expected(): string | undefined {
|
|
46
|
+
return this._expected ?? this.failedTestInfo?.checkResultDiff?.expected
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
set expected(value: string | undefined) {
|
|
50
|
+
this._expected = value
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
get actual(): string | undefined {
|
|
54
|
+
return this._actual ?? this.failedTestInfo?.checkResultDiff?.actual
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
set actual(value: string | undefined) {
|
|
58
|
+
this._actual = value
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
toCheckResult(status: CheckStatus): CheckResult {
|
|
62
|
+
const expected = this.expected
|
|
63
|
+
const actual = this.actual
|
|
64
|
+
if ((expected === undefined && actual !== undefined) || (expected !== undefined && actual === undefined)) {
|
|
65
|
+
logger("CheckFeedback").warn("Expected/Actual: one value is missing. Second value would be ignored")
|
|
66
|
+
}
|
|
67
|
+
const diff =
|
|
68
|
+
expected !== undefined && actual !== undefined ? new CheckResultDiff(expected, actual) : undefined
|
|
69
|
+
return new CheckResult(status, this.message, undefined, diff)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { CheckResultSeverity } from "./CheckResultSeverity"
|
|
2
|
+
import { CheckStatus } from "./CheckStatus"
|
|
3
|
+
import { FAILED_TO_CHECK_URL, LOGIN_NEEDED_MESSAGE, NO_TESTS_URL } from "./EduFormatNames"
|
|
4
|
+
import { message } from "./uiMessages"
|
|
5
|
+
import { EduTestInfo } from "./EduTestInfo"
|
|
6
|
+
|
|
7
|
+
export class CheckResult {
|
|
8
|
+
readonly status: CheckStatus
|
|
9
|
+
readonly severity: CheckResultSeverity
|
|
10
|
+
readonly hyperlinkAction?: () => void
|
|
11
|
+
readonly executedTestsInfo: EduTestInfo[]
|
|
12
|
+
|
|
13
|
+
private readonly firstFailedTest?: EduTestInfo
|
|
14
|
+
|
|
15
|
+
details?: string
|
|
16
|
+
diff?: CheckResultDiff
|
|
17
|
+
messageText: string
|
|
18
|
+
|
|
19
|
+
constructor(
|
|
20
|
+
status: CheckStatus,
|
|
21
|
+
messageText = "",
|
|
22
|
+
details?: string | null,
|
|
23
|
+
diff?: CheckResultDiff | null,
|
|
24
|
+
severity: CheckResultSeverity = CheckResultSeverity.Info,
|
|
25
|
+
hyperlinkAction?: (() => void) | null,
|
|
26
|
+
executedTestsInfo: EduTestInfo[] = []
|
|
27
|
+
) {
|
|
28
|
+
this.status = status
|
|
29
|
+
this.severity = severity
|
|
30
|
+
this.hyperlinkAction = hyperlinkAction ?? undefined
|
|
31
|
+
this.executedTestsInfo = executedTestsInfo
|
|
32
|
+
this.firstFailedTest = this.executedTestsInfo.find((test) => !test.isSuccess)
|
|
33
|
+
this.details = details ?? undefined
|
|
34
|
+
this.diff = diff ?? undefined
|
|
35
|
+
this.messageText = messageText
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
get fullMessage(): string {
|
|
39
|
+
return this.details ? `${this.messageText}\n\n${this.details}` : this.messageText
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
get isSolved(): boolean {
|
|
43
|
+
return this.status === CheckStatus.Solved
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
get detailsResolved(): string | undefined {
|
|
47
|
+
return this.details ?? this.firstFailedTest?.details
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
get diffResolved(): CheckResultDiff | undefined {
|
|
51
|
+
return this.diff ?? this.firstFailedTest?.checkResultDiff
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
get messageResolved(): string {
|
|
55
|
+
return this.messageText || this.firstFailedTest?.message || ""
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
static NO_LOCAL_CHECK = new CheckResult(
|
|
59
|
+
CheckStatus.Unchecked,
|
|
60
|
+
message("check.result.local.check.unavailable")
|
|
61
|
+
)
|
|
62
|
+
static LOGIN_NEEDED = new CheckResult(CheckStatus.Unchecked, LOGIN_NEEDED_MESSAGE)
|
|
63
|
+
static CONNECTION_FAILED = new CheckResult(
|
|
64
|
+
CheckStatus.Unchecked,
|
|
65
|
+
message("check.result.connection.failed")
|
|
66
|
+
)
|
|
67
|
+
static SOLVED = new CheckResult(CheckStatus.Solved)
|
|
68
|
+
static CANCELED = new CheckResult(CheckStatus.Unchecked, message("check.result.canceled"))
|
|
69
|
+
static UNCHECKED = new CheckResult(CheckStatus.Unchecked)
|
|
70
|
+
|
|
71
|
+
static get noTestsRun(): CheckResult {
|
|
72
|
+
return new CheckResult(
|
|
73
|
+
CheckStatus.Unchecked,
|
|
74
|
+
message("check.no.tests.with.help.guide", NO_TESTS_URL)
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
static get failedToCheck(): CheckResult {
|
|
79
|
+
return new CheckResult(
|
|
80
|
+
CheckStatus.Unchecked,
|
|
81
|
+
message("error.failed.to.launch.checking.with.help.guide", FAILED_TO_CHECK_URL)
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export class CheckResultDiff {
|
|
87
|
+
constructor(
|
|
88
|
+
readonly expected: string,
|
|
89
|
+
readonly actual: string,
|
|
90
|
+
readonly title = ""
|
|
91
|
+
) {}
|
|
92
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export enum CheckResultSeverity {
|
|
2
|
+
Info = "Info",
|
|
3
|
+
Warning = "Warning",
|
|
4
|
+
Error = "Error",
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function isWarning(severity: CheckResultSeverity): boolean {
|
|
8
|
+
return severity === CheckResultSeverity.Warning
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function isInfo(severity: CheckResultSeverity): boolean {
|
|
12
|
+
return severity === CheckResultSeverity.Info
|
|
13
|
+
}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { LessonContainer } from "./LessonContainer"
|
|
2
|
+
import { CourseMode } from "./CourseMode"
|
|
3
|
+
import { CourseVisibility } from "./CourseVisibility"
|
|
4
|
+
import { EduFile } from "./EduFile"
|
|
5
|
+
import { PluginInfo } from "./PluginInfo"
|
|
6
|
+
import { UserInfo } from "./UserInfo"
|
|
7
|
+
import { Vendor } from "./Vendor"
|
|
8
|
+
import { LESSON, OBJECTIVE_C, CPP, PYCHARM, DEFAULT_ENVIRONMENT } from "./EduFormatNames"
|
|
9
|
+
import { Lesson } from "./Lesson"
|
|
10
|
+
import { Section } from "./Section"
|
|
11
|
+
import { ItemContainer } from "./ItemContainer"
|
|
12
|
+
import { Language } from "./Language"
|
|
13
|
+
|
|
14
|
+
export abstract class Course extends LessonContainer {
|
|
15
|
+
description = ""
|
|
16
|
+
environment = DEFAULT_ENVIRONMENT
|
|
17
|
+
environmentSettings: Record<string, string> = {}
|
|
18
|
+
courseMode: CourseMode = CourseMode.STUDENT
|
|
19
|
+
solutionsHidden = false
|
|
20
|
+
disabledFeatures: string[] = []
|
|
21
|
+
visibility: CourseVisibility = CourseVisibility.LocalVisibility
|
|
22
|
+
additionalFiles: EduFile[] = []
|
|
23
|
+
pluginDependencies: PluginInfo[] = []
|
|
24
|
+
private nonEditableFiles: Set<string> = new Set()
|
|
25
|
+
authors: UserInfo[] = []
|
|
26
|
+
isLocal = false
|
|
27
|
+
needWriteYamlText = false
|
|
28
|
+
languageCode = "en"
|
|
29
|
+
|
|
30
|
+
get type(): string {
|
|
31
|
+
return this.itemType
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
get title(): string {
|
|
35
|
+
return this.name
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
set title(value: string) {
|
|
39
|
+
this.name = value
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
get language(): string {
|
|
43
|
+
return this.languageCode
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
get content(): Lesson[] {
|
|
47
|
+
return this.lessons
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
isMarketplace = false
|
|
51
|
+
vendor?: Vendor
|
|
52
|
+
marketplaceCourseVersion = 0
|
|
53
|
+
organization?: string
|
|
54
|
+
isMarketplacePrivate = false
|
|
55
|
+
createDate: Date = new Date(0)
|
|
56
|
+
feedbackLink?: string
|
|
57
|
+
license?: string
|
|
58
|
+
|
|
59
|
+
customContentPath = ""
|
|
60
|
+
|
|
61
|
+
private _languageId = ""
|
|
62
|
+
languageVersion?: string
|
|
63
|
+
private _programmingLanguage = ""
|
|
64
|
+
|
|
65
|
+
get programmingLanguage(): string {
|
|
66
|
+
return this._programmingLanguage
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
set programmingLanguage(value: string | null) {
|
|
70
|
+
if (!value) return
|
|
71
|
+
this._programmingLanguage = value
|
|
72
|
+
const parts = value.split(" ")
|
|
73
|
+
this.languageId = parts[0]
|
|
74
|
+
this.languageVersion = parts[1]
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
get languageId(): string {
|
|
78
|
+
if (this._languageId === OBJECTIVE_C || this._languageId === CPP) {
|
|
79
|
+
return Language.findLanguageByName("C/C++") ?? this._languageId
|
|
80
|
+
}
|
|
81
|
+
return this._languageId
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
set languageId(value: string) {
|
|
85
|
+
this._languageId = value
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
initCourse(isRestarted: boolean): void {
|
|
89
|
+
this.init(this, isRestarted)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
override init(parentItem: ItemContainer, isRestarted: boolean): void {
|
|
93
|
+
if (!(parentItem instanceof Course)) {
|
|
94
|
+
throw new Error("Parent for course must be itself")
|
|
95
|
+
}
|
|
96
|
+
super.init(parentItem, isRestarted)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
getLesson(sectionName?: string, lessonName?: string): Lesson | undefined
|
|
100
|
+
getLesson(param?: string | number | ((lesson: Lesson) => boolean)): Lesson | undefined
|
|
101
|
+
getLesson(param1?: string | number | ((lesson: Lesson) => boolean), param2?: string): Lesson | undefined {
|
|
102
|
+
if (typeof param1 === "string" && param2 !== undefined) {
|
|
103
|
+
const sectionName = param1
|
|
104
|
+
const lessonName = param2
|
|
105
|
+
if (sectionName) {
|
|
106
|
+
const section = this.getSection(sectionName)
|
|
107
|
+
if (section) {
|
|
108
|
+
return section.getLesson(lessonName)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return this.lessons.find((lesson) => lesson.name === lessonName)
|
|
112
|
+
}
|
|
113
|
+
if (param1 === undefined) return undefined
|
|
114
|
+
if (typeof param1 === "string") {
|
|
115
|
+
return super.getLesson(param1)
|
|
116
|
+
}
|
|
117
|
+
if (typeof param1 === "number") {
|
|
118
|
+
return super.getLesson(param1)
|
|
119
|
+
}
|
|
120
|
+
return super.getLesson(param1 as (lesson: Lesson) => boolean)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
get sections(): Section[] {
|
|
124
|
+
return this.items.filter((item): item is Section => item instanceof Section)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
addSection(section: Section): void {
|
|
128
|
+
this.addItem(section)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
removeSection(section: Section): void {
|
|
132
|
+
this.removeItem(section)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
getSection(name: string): Section | undefined
|
|
136
|
+
getSection(predicate: (section: Section) => boolean): Section | undefined
|
|
137
|
+
getSection(param: string | ((section: Section) => boolean)): Section | undefined {
|
|
138
|
+
if (typeof param === "string") {
|
|
139
|
+
return this.sections.find((section) => section.name === param)
|
|
140
|
+
}
|
|
141
|
+
return this.sections.find(param)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
get course(): Course {
|
|
145
|
+
return this
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
get itemType(): string {
|
|
149
|
+
return PYCHARM
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
get isStudy(): boolean {
|
|
153
|
+
return this.courseMode === CourseMode.STUDENT
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
override sortItems(): void {
|
|
157
|
+
super.sortItems()
|
|
158
|
+
this.sections.forEach((section) => section.sortItems())
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
toString(): string {
|
|
162
|
+
return this.name
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
get authorFullNames(): string[] {
|
|
166
|
+
return this.organization ? [this.organization] : this.authors.map((author) => author.getFullName())
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
get humanLanguage(): string {
|
|
170
|
+
try {
|
|
171
|
+
return new Intl.DisplayNames([this.languageCode], { type: "language" }).of(this.languageCode) ?? this.languageCode
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
return this.languageCode
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
get isStepikRemote(): boolean {
|
|
179
|
+
return false
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
incrementMarketplaceCourseVersion(remoteCourseVersion: number): void {
|
|
183
|
+
this.marketplaceCourseVersion = remoteCourseVersion + 1
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
isEditableFile(path: string): boolean {
|
|
187
|
+
return !this.nonEditableFiles.has(path)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
addNonEditableFile(path?: string | null): void {
|
|
191
|
+
if (path && this.isStudy) {
|
|
192
|
+
this.nonEditableFiles.add(path)
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
removeNonEditableFile(path?: string | null): void {
|
|
197
|
+
if (path && this.isStudy) {
|
|
198
|
+
this.nonEditableFiles.delete(path)
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|