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.
Files changed (239) hide show
  1. package/.idea/edu-format-ts.iml +10 -0
  2. package/.idea/inspectionProfiles/Project_Default.xml +28 -0
  3. package/.idea/misc.xml +4 -0
  4. package/.idea/modules.xml +8 -0
  5. package/.idea/vcs.xml +6 -0
  6. package/AGENTS.md +1 -0
  7. package/dist/courseFormat/AnswerPlaceholder.d.ts +37 -0
  8. package/dist/courseFormat/AnswerPlaceholder.js +101 -0
  9. package/dist/courseFormat/AnswerPlaceholderComparator.d.ts +4 -0
  10. package/dist/courseFormat/AnswerPlaceholderComparator.js +8 -0
  11. package/dist/courseFormat/AnswerPlaceholderDependency.d.ts +19 -0
  12. package/dist/courseFormat/AnswerPlaceholderDependency.js +91 -0
  13. package/dist/courseFormat/CheckFeedback.d.ts +20 -0
  14. package/dist/courseFormat/CheckFeedback.js +58 -0
  15. package/dist/courseFormat/CheckResult.d.ts +33 -0
  16. package/dist/courseFormat/CheckResult.js +58 -0
  17. package/dist/courseFormat/CheckResultSeverity.d.ts +7 -0
  18. package/dist/courseFormat/CheckResultSeverity.js +17 -0
  19. package/dist/courseFormat/CheckStatus.d.ts +5 -0
  20. package/dist/courseFormat/CheckStatus.js +9 -0
  21. package/dist/courseFormat/Course.d.ts +68 -0
  22. package/dist/courseFormat/Course.js +165 -0
  23. package/dist/courseFormat/CourseMode.d.ts +4 -0
  24. package/dist/courseFormat/CourseMode.js +8 -0
  25. package/dist/courseFormat/CourseVisibility.d.ts +4 -0
  26. package/dist/courseFormat/CourseVisibility.js +8 -0
  27. package/dist/courseFormat/CourseraCourse.d.ts +5 -0
  28. package/dist/courseFormat/CourseraCourse.js +15 -0
  29. package/dist/courseFormat/DescriptionFormat.d.ts +5 -0
  30. package/dist/courseFormat/DescriptionFormat.js +9 -0
  31. package/dist/courseFormat/EduCourse.d.ts +17 -0
  32. package/dist/courseFormat/EduCourse.js +42 -0
  33. package/dist/courseFormat/EduFile.d.ts +22 -0
  34. package/dist/courseFormat/EduFile.js +110 -0
  35. package/dist/courseFormat/EduFileErrorHighlightLevel.d.ts +5 -0
  36. package/dist/courseFormat/EduFileErrorHighlightLevel.js +9 -0
  37. package/dist/courseFormat/EduFormatNames.d.ts +75 -0
  38. package/dist/courseFormat/EduFormatNames.js +80 -0
  39. package/dist/courseFormat/EduTestInfo.d.ts +28 -0
  40. package/dist/courseFormat/EduTestInfo.js +68 -0
  41. package/dist/courseFormat/EduVersions.d.ts +2 -0
  42. package/dist/courseFormat/EduVersions.js +5 -0
  43. package/dist/courseFormat/FileContents.d.ts +36 -0
  44. package/dist/courseFormat/FileContents.js +67 -0
  45. package/dist/courseFormat/FileContentsFactory.d.ts +17 -0
  46. package/dist/courseFormat/FileContentsFactory.js +2 -0
  47. package/dist/courseFormat/FrameworkLesson.d.ts +9 -0
  48. package/dist/courseFormat/FrameworkLesson.js +28 -0
  49. package/dist/courseFormat/ItemContainer.d.ts +13 -0
  50. package/dist/courseFormat/ItemContainer.js +45 -0
  51. package/dist/courseFormat/JBAccountUserInfo.d.ts +9 -0
  52. package/dist/courseFormat/JBAccountUserInfo.js +20 -0
  53. package/dist/courseFormat/Language.d.ts +4 -0
  54. package/dist/courseFormat/Language.js +33 -0
  55. package/dist/courseFormat/Lesson.d.ts +20 -0
  56. package/dist/courseFormat/Lesson.js +56 -0
  57. package/dist/courseFormat/LessonContainer.d.ts +16 -0
  58. package/dist/courseFormat/LessonContainer.js +54 -0
  59. package/dist/courseFormat/PluginInfo.d.ts +7 -0
  60. package/dist/courseFormat/PluginInfo.js +15 -0
  61. package/dist/courseFormat/Section.d.ts +8 -0
  62. package/dist/courseFormat/Section.js +27 -0
  63. package/dist/courseFormat/StudyItem.d.ts +20 -0
  64. package/dist/courseFormat/StudyItem.js +47 -0
  65. package/dist/courseFormat/Tags.d.ts +16 -0
  66. package/dist/courseFormat/Tags.js +42 -0
  67. package/dist/courseFormat/TaskFile.d.ts +26 -0
  68. package/dist/courseFormat/TaskFile.js +72 -0
  69. package/dist/courseFormat/UserInfo.d.ts +3 -0
  70. package/dist/courseFormat/UserInfo.js +2 -0
  71. package/dist/courseFormat/Vendor.d.ts +7 -0
  72. package/dist/courseFormat/Vendor.js +14 -0
  73. package/dist/courseFormat/attempts/Attempt.d.ts +12 -0
  74. package/dist/courseFormat/attempts/Attempt.js +25 -0
  75. package/dist/courseFormat/attempts/AttemptBase.d.ts +9 -0
  76. package/dist/courseFormat/attempts/AttemptBase.js +28 -0
  77. package/dist/courseFormat/attempts/DataTaskAttempt.d.ts +6 -0
  78. package/dist/courseFormat/attempts/DataTaskAttempt.js +24 -0
  79. package/dist/courseFormat/attempts/Dataset.d.ts +12 -0
  80. package/dist/courseFormat/attempts/Dataset.js +21 -0
  81. package/dist/courseFormat/fileUtils.d.ts +5 -0
  82. package/dist/courseFormat/fileUtils.js +32 -0
  83. package/dist/courseFormat/hyperskill/HyperskillCourse.d.ts +13 -0
  84. package/dist/courseFormat/hyperskill/HyperskillCourse.js +25 -0
  85. package/dist/courseFormat/hyperskill/HyperskillProject.d.ts +10 -0
  86. package/dist/courseFormat/hyperskill/HyperskillProject.js +16 -0
  87. package/dist/courseFormat/hyperskill/HyperskillStage.d.ts +8 -0
  88. package/dist/courseFormat/hyperskill/HyperskillStage.js +20 -0
  89. package/dist/courseFormat/hyperskill/HyperskillTaskType.d.ts +4 -0
  90. package/dist/courseFormat/hyperskill/HyperskillTaskType.js +26 -0
  91. package/dist/courseFormat/hyperskill/HyperskillTopic.d.ts +5 -0
  92. package/dist/courseFormat/hyperskill/HyperskillTopic.js +11 -0
  93. package/dist/courseFormat/loggerUtils.d.ts +1 -0
  94. package/dist/courseFormat/loggerUtils.js +6 -0
  95. package/dist/courseFormat/stepik/StepikCourse.d.ts +5 -0
  96. package/dist/courseFormat/stepik/StepikCourse.js +15 -0
  97. package/dist/courseFormat/stepik/StepikLesson.d.ts +6 -0
  98. package/dist/courseFormat/stepik/StepikLesson.js +16 -0
  99. package/dist/courseFormat/tasks/AnswerTask.d.ts +8 -0
  100. package/dist/courseFormat/tasks/AnswerTask.js +11 -0
  101. package/dist/courseFormat/tasks/CodeTask.d.ts +12 -0
  102. package/dist/courseFormat/tasks/CodeTask.js +21 -0
  103. package/dist/courseFormat/tasks/DataTask.d.ts +18 -0
  104. package/dist/courseFormat/tasks/DataTask.js +32 -0
  105. package/dist/courseFormat/tasks/EduTask.d.ts +12 -0
  106. package/dist/courseFormat/tasks/EduTask.js +22 -0
  107. package/dist/courseFormat/tasks/IdeTask.d.ts +9 -0
  108. package/dist/courseFormat/tasks/IdeTask.js +14 -0
  109. package/dist/courseFormat/tasks/NumberTask.d.ts +9 -0
  110. package/dist/courseFormat/tasks/NumberTask.js +14 -0
  111. package/dist/courseFormat/tasks/OutputTask.d.ts +10 -0
  112. package/dist/courseFormat/tasks/OutputTask.js +18 -0
  113. package/dist/courseFormat/tasks/OutputTaskBase.d.ts +14 -0
  114. package/dist/courseFormat/tasks/OutputTaskBase.js +19 -0
  115. package/dist/courseFormat/tasks/RemoteEduTask.d.ts +9 -0
  116. package/dist/courseFormat/tasks/RemoteEduTask.js +15 -0
  117. package/dist/courseFormat/tasks/StringTask.d.ts +9 -0
  118. package/dist/courseFormat/tasks/StringTask.js +14 -0
  119. package/dist/courseFormat/tasks/TableTask.d.ts +17 -0
  120. package/dist/courseFormat/tasks/TableTask.js +43 -0
  121. package/dist/courseFormat/tasks/Task.d.ts +45 -0
  122. package/dist/courseFormat/tasks/Task.js +155 -0
  123. package/dist/courseFormat/tasks/TheoryTask.d.ts +10 -0
  124. package/dist/courseFormat/tasks/TheoryTask.js +15 -0
  125. package/dist/courseFormat/tasks/UnsupportedTask.d.ts +9 -0
  126. package/dist/courseFormat/tasks/UnsupportedTask.js +14 -0
  127. package/dist/courseFormat/tasks/choice/ChoiceOption.d.ts +10 -0
  128. package/dist/courseFormat/tasks/choice/ChoiceOption.js +33 -0
  129. package/dist/courseFormat/tasks/choice/ChoiceOptionStatus.d.ts +5 -0
  130. package/dist/courseFormat/tasks/choice/ChoiceOptionStatus.js +9 -0
  131. package/dist/courseFormat/tasks/choice/ChoiceTask.d.ts +23 -0
  132. package/dist/courseFormat/tasks/choice/ChoiceTask.js +47 -0
  133. package/dist/courseFormat/tasks/matching/MatchingTask.d.ts +10 -0
  134. package/dist/courseFormat/tasks/matching/MatchingTask.js +15 -0
  135. package/dist/courseFormat/tasks/matching/SortingBasedTask.d.ts +16 -0
  136. package/dist/courseFormat/tasks/matching/SortingBasedTask.js +50 -0
  137. package/dist/courseFormat/tasks/matching/SortingTask.d.ts +9 -0
  138. package/dist/courseFormat/tasks/matching/SortingTask.js +14 -0
  139. package/dist/courseFormat/uiMessages.d.ts +3 -0
  140. package/dist/courseFormat/uiMessages.js +14 -0
  141. package/dist/disk-loader.d.ts +4 -0
  142. package/dist/disk-loader.js +389 -0
  143. package/dist/index.d.ts +31 -0
  144. package/dist/index.js +64 -0
  145. package/dist/loader.d.ts +7 -0
  146. package/dist/loader.js +435 -0
  147. package/dist/models.d.ts +49 -0
  148. package/dist/models.js +2 -0
  149. package/dist/zip-loader.d.ts +4 -0
  150. package/dist/zip-loader.js +431 -0
  151. package/example-course-project/course-info.yaml +15 -0
  152. package/example-course-project/lesson1/lesson-info.yaml +3 -0
  153. package/example-course-project/lesson1/lesson-remote-info.yaml +1 -0
  154. package/example-course-project/lesson1/task1/Task.txt +1 -0
  155. package/example-course-project/lesson1/task1/task-info.yaml +12 -0
  156. package/example-course-project/lesson1/task1/task-remote-info.yaml +1 -0
  157. package/example-course-project/lesson1/task1/task.md +47 -0
  158. package/example-course-project/lesson1/task1/tests/Tests.txt +0 -0
  159. package/example-course-project/lesson1/task2/Task.txt +1 -0
  160. package/example-course-project/lesson1/task2/task-info.yaml +12 -0
  161. package/example-course-project/lesson1/task2/task-remote-info.yaml +1 -0
  162. package/example-course-project/lesson1/task2/task.md +47 -0
  163. package/example-course-project/lesson1/task2/tests/Tests.txt +0 -0
  164. package/package.json +19 -0
  165. package/src/@types/mime-types.d.ts +3 -0
  166. package/src/courseFormat/AnswerPlaceholder.ts +121 -0
  167. package/src/courseFormat/AnswerPlaceholderComparator.ts +7 -0
  168. package/src/courseFormat/AnswerPlaceholderDependency.ts +122 -0
  169. package/src/courseFormat/CheckFeedback.ts +71 -0
  170. package/src/courseFormat/CheckResult.ts +92 -0
  171. package/src/courseFormat/CheckResultSeverity.ts +13 -0
  172. package/src/courseFormat/CheckStatus.ts +5 -0
  173. package/src/courseFormat/Course.ts +201 -0
  174. package/src/courseFormat/CourseMode.ts +4 -0
  175. package/src/courseFormat/CourseVisibility.ts +4 -0
  176. package/src/courseFormat/CourseraCourse.ts +10 -0
  177. package/src/courseFormat/DescriptionFormat.ts +5 -0
  178. package/src/courseFormat/EduCourse.ts +41 -0
  179. package/src/courseFormat/EduFile.ts +133 -0
  180. package/src/courseFormat/EduFileErrorHighlightLevel.ts +5 -0
  181. package/src/courseFormat/EduFormatNames.ts +95 -0
  182. package/src/courseFormat/EduTestInfo.ts +87 -0
  183. package/src/courseFormat/EduVersions.ts +2 -0
  184. package/src/courseFormat/FileContents.ts +97 -0
  185. package/src/courseFormat/FileContentsFactory.ts +19 -0
  186. package/src/courseFormat/FrameworkLesson.ts +29 -0
  187. package/src/courseFormat/ItemContainer.ts +47 -0
  188. package/src/courseFormat/JBAccountUserInfo.ts +21 -0
  189. package/src/courseFormat/Language.ts +31 -0
  190. package/src/courseFormat/Lesson.ts +69 -0
  191. package/src/courseFormat/LessonContainer.ts +65 -0
  192. package/src/courseFormat/PluginInfo.ts +15 -0
  193. package/src/courseFormat/Section.ts +29 -0
  194. package/src/courseFormat/StudyItem.ts +55 -0
  195. package/src/courseFormat/Tags.ts +45 -0
  196. package/src/courseFormat/TaskFile.ts +88 -0
  197. package/src/courseFormat/UserInfo.ts +3 -0
  198. package/src/courseFormat/Vendor.ts +15 -0
  199. package/src/courseFormat/attempts/Attempt.ts +28 -0
  200. package/src/courseFormat/attempts/AttemptBase.ts +24 -0
  201. package/src/courseFormat/attempts/DataTaskAttempt.ts +19 -0
  202. package/src/courseFormat/attempts/Dataset.ts +13 -0
  203. package/src/courseFormat/fileUtils.ts +31 -0
  204. package/src/courseFormat/hyperskill/HyperskillCourse.ts +24 -0
  205. package/src/courseFormat/hyperskill/HyperskillProject.ts +10 -0
  206. package/src/courseFormat/hyperskill/HyperskillStage.ts +15 -0
  207. package/src/courseFormat/hyperskill/HyperskillTaskType.ts +23 -0
  208. package/src/courseFormat/hyperskill/HyperskillTopic.ts +5 -0
  209. package/src/courseFormat/loggerUtils.ts +3 -0
  210. package/src/courseFormat/stepik/StepikCourse.ts +10 -0
  211. package/src/courseFormat/stepik/StepikLesson.ts +11 -0
  212. package/src/courseFormat/tasks/AnswerTask.ts +13 -0
  213. package/src/courseFormat/tasks/CodeTask.ts +42 -0
  214. package/src/courseFormat/tasks/DataTask.ts +37 -0
  215. package/src/courseFormat/tasks/EduTask.ts +26 -0
  216. package/src/courseFormat/tasks/IdeTask.ts +17 -0
  217. package/src/courseFormat/tasks/NumberTask.ts +17 -0
  218. package/src/courseFormat/tasks/OutputTask.ts +21 -0
  219. package/src/courseFormat/tasks/OutputTaskBase.ts +23 -0
  220. package/src/courseFormat/tasks/RemoteEduTask.ts +18 -0
  221. package/src/courseFormat/tasks/StringTask.ts +17 -0
  222. package/src/courseFormat/tasks/TableTask.ts +51 -0
  223. package/src/courseFormat/tasks/Task.ts +181 -0
  224. package/src/courseFormat/tasks/TheoryTask.ts +19 -0
  225. package/src/courseFormat/tasks/UnsupportedTask.ts +17 -0
  226. package/src/courseFormat/tasks/choice/ChoiceOption.ts +37 -0
  227. package/src/courseFormat/tasks/choice/ChoiceOptionStatus.ts +5 -0
  228. package/src/courseFormat/tasks/choice/ChoiceTask.ts +57 -0
  229. package/src/courseFormat/tasks/matching/MatchingTask.ts +19 -0
  230. package/src/courseFormat/tasks/matching/SortingBasedTask.ts +59 -0
  231. package/src/courseFormat/tasks/matching/SortingTask.ts +17 -0
  232. package/src/courseFormat/uiMessages.ts +12 -0
  233. package/src/disk-loader.ts +463 -0
  234. package/src/index.ts +33 -0
  235. package/src/models.ts +54 -0
  236. package/src/zip-loader.ts +583 -0
  237. package/test/load-course.test.js +279 -0
  238. package/test/load-zip-course.test.js +73 -0
  239. 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,7 @@
1
+ import { AnswerPlaceholder } from "./AnswerPlaceholder"
2
+
3
+ export const AnswerPlaceholderComparator = {
4
+ compare(a: AnswerPlaceholder, b: AnswerPlaceholder): number {
5
+ return a.offset - b.offset
6
+ },
7
+ }
@@ -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,5 @@
1
+ export enum CheckStatus {
2
+ Unchecked = "Unchecked",
3
+ Failed = "Failed",
4
+ Solved = "Solved",
5
+ }
@@ -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
+ }
@@ -0,0 +1,4 @@
1
+ export enum CourseMode {
2
+ STUDENT = "STUDENT",
3
+ EDUCATOR = "EDUCATOR",
4
+ }
@@ -0,0 +1,4 @@
1
+ export enum CourseVisibility {
2
+ LocalVisibility = "LocalVisibility",
3
+ RemoteVisibility = "RemoteVisibility",
4
+ }
@@ -0,0 +1,10 @@
1
+ import { Course } from "./Course"
2
+ import { COURSERA } from "./EduFormatNames"
3
+
4
+ export class CourseraCourse extends Course {
5
+ submitManually = false
6
+
7
+ override get itemType(): string {
8
+ return COURSERA
9
+ }
10
+ }
@@ -0,0 +1,5 @@
1
+ export enum DescriptionFormat {
2
+ HTML = "HTML",
3
+ MD = "MD",
4
+ TEXT = "TEXT",
5
+ }